Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding FromArgs::help_json_from_args() to get JSON encoded help message #115

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ pub trait FromArgs: Sized {
///
/// Options:
/// --help display usage information
/// --help-json display usage information encoded in JSON
///
/// Commands:
/// list list all the classes.
Expand All @@ -282,6 +283,7 @@ pub trait FromArgs: Sized {
/// Options:
/// --teacher-name list classes for only this teacher.
/// --help display usage information
/// --help-json display usage information encoded in JSON
/// "#.to_string(),
/// status: Ok(()),
/// },
Expand Down Expand Up @@ -424,6 +426,7 @@ pub trait FromArgs: Sized {
///
/// Options:
/// --help display usage information
/// --help-json display usage information encoded in JSON
///
/// Commands:
/// list list all the classes.
Expand Down Expand Up @@ -668,7 +671,8 @@ impl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];
/// `parse_options`: Helper to parse optional arguments.
/// `parse_positionals`: Helper to parse positional arguments.
/// `parse_subcommand`: Helper to parse a subcommand.
/// `help_func`: Generate a help message.
/// `help_func`: Generate a help message as plain text.
/// `help_json_func`: Generate a help message serialized into JSON.
#[doc(hidden)]
pub fn parse_struct_args(
cmd_name: &[&str],
Expand All @@ -677,19 +681,29 @@ pub fn parse_struct_args(
mut parse_positionals: ParseStructPositionals<'_>,
mut parse_subcommand: Option<ParseStructSubCommand<'_>>,
help_func: &dyn Fn() -> String,
help_json_func: &dyn Fn() -> String,
) -> Result<(), EarlyExit> {
let mut help = false;
let mut help_json = false;
let mut remaining_args = args;
let mut positional_index = 0;
let mut options_ended = false;

'parse_args: while let Some(&next_arg) = remaining_args.get(0) {
remaining_args = &remaining_args[1..];

if (next_arg == "--help" || next_arg == "help") && !options_ended {
help = true;
continue;
}

// look for help-json for json formatted help output.
if (next_arg == "--help-json" || next_arg == "help-json") && !options_ended {
claywilkinson marked this conversation as resolved.
Show resolved Hide resolved
help = true;
help_json = true;
continue;
}

if next_arg.starts_with("-") && !options_ended {
if next_arg == "--" {
options_ended = true;
Expand All @@ -714,8 +728,9 @@ pub fn parse_struct_args(

parse_positionals.parse(&mut positional_index, next_arg)?;
}

if help {
if help_json {
Err(EarlyExit { output: help_json_func(), status: Ok(()) })
} else if help {
Err(EarlyExit { output: help_func(), status: Ok(()) })
} else {
Ok(())
Expand Down
148 changes: 146 additions & 2 deletions argh/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,105 @@ fn subcommand_example() {
assert_eq!(two, TopLevel { nested: MySubCommandEnum::Two(SubCommandTwo { fooey: true }) },);
}

#[test]
fn help_json_test_subcommand() {
#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
#[argh(subcommand)]
nested: MySubCommandEnum,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
One(SubCommandOne),
Two(SubCommandTwo),
}

#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one")]
struct SubCommandOne {
#[argh(option)]
/// how many x
x: usize,
}

#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
struct SubCommandTwo {
#[argh(switch)]
/// whether to fooey
fooey: bool,
}

assert_help_json_string::<TopLevel>(
vec!["--help-json"],
r###"{
"usage": "test_arg_0 <command> [<args>]",
"description": "Top-level command.",
"options": [{"short": "", "long": "--help", "description": "display usage information"},
{"short": "", "long": "--help-json", "description": "display usage information encoded in JSON"}],
"positional": [],
"examples": "",
"notes": "",
"error_codes": [],
"subcommands": [{"name": "one", "description": "First subcommand."},
{"name": "two", "description": "Second subcommand."}]
}
"###,
);

assert_help_json_string::<TopLevel>(
vec!["one", "--help-json"],
r###"{
"usage": "test_arg_0 one --x <x>",
"description": "First subcommand.",
"options": [{"short": "", "long": "--x", "description": "how many x"},
{"short": "", "long": "--help", "description": "display usage information"},
{"short": "", "long": "--help-json", "description": "display usage information encoded in JSON"}],
"positional": [],
"examples": "",
"notes": "",
"error_codes": [],
"subcommands": []
}
"###,
);
}

#[test]
fn help_json_test_multiline_doc_comment() {
claywilkinson marked this conversation as resolved.
Show resolved Hide resolved
#[derive(FromArgs)]
/// Short description
struct Cmd {
#[argh(switch)]
/// a switch with a description
/// that is spread across
/// a number of
/// lines of comments.
_s: bool,
}
assert_help_json_string::<Cmd>(
vec!["--help-json"],
r###"{
"usage": "test_arg_0 [--s]",
"description": "Short description",
"options": [{"short": "", "long": "--s", "description": "a switch with a description that is spread across a number of lines of comments."},
{"short": "", "long": "--help", "description": "display usage information"},
{"short": "", "long": "--help-json", "description": "display usage information encoded in JSON"}],
"positional": [],
"examples": "",
"notes": "",
"error_codes": [],
"subcommands": []
}
"###,
);
}

#[test]
fn multiline_doc_comment_description() {
#[derive(FromArgs)]
Expand All @@ -108,6 +207,7 @@ Options:
--s a switch with a description that is spread across a number
of lines of comments.
--help display usage information
--help-json display usage information encoded in JSON
"###,
);
}
Expand Down Expand Up @@ -195,6 +295,16 @@ fn assert_help_string<T: FromArgs>(help_str: &str) {
}
}

fn assert_help_json_string<T: FromArgs>(args: Vec<&str>, help_str: &str) {
match T::from_args(&["test_arg_0"], &args) {
Ok(_) => panic!("help-json was parsed as args"),
Err(e) => {
assert_eq!(help_str, e.output);
e.status.expect("help-json returned an error");
}
}
}

fn assert_output<T: FromArgs + Debug + PartialEq>(args: &[&str], expected: T) {
let t = T::from_args(&["cmd"], args).expect("failed to parse");
assert_eq!(t, expected);
Expand Down Expand Up @@ -245,6 +355,7 @@ Woot
Options:
-n, --n fooey
--help display usage information
--help-json display usage information encoded in JSON
"###,
);
}
Expand All @@ -267,6 +378,7 @@ Woot
Options:
--option-name fooey
--help display usage information
--help-json display usage information encoded in JSON
"###,
);
}
Expand Down Expand Up @@ -305,6 +417,7 @@ Positional Arguments:

Options:
--help display usage information
--help-json display usage information encoded in JSON
"###,
);
}
Expand Down Expand Up @@ -694,6 +807,7 @@ A type for testing `--help`/`help`

Options:
--help display usage information
--help-json display usage information encoded in JSON

Commands:
first First subcommmand for testing `help`.
Expand All @@ -705,6 +819,7 @@ First subcommmand for testing `help`.

Options:
--help display usage information
--help-json display usage information encoded in JSON

Commands:
second Second subcommand for testing `help`.
Expand All @@ -716,6 +831,7 @@ Second subcommand for testing `help`.

Options:
--help display usage information
--help-json display usage information encoded in JSON
"###;

#[test]
Expand Down Expand Up @@ -758,7 +874,7 @@ Options:

#[derive(FromArgs, PartialEq, Debug)]
#[argh(
description = "Destroy the contents of <file>.",
description = "Destroy the contents of <file> with a specific \"method of destruction\".",
example = "Scribble 'abc' and then run |grind|.\n$ {command_name} -s 'abc' grind old.txt taxes.cp",
note = "Use `{command_name} help <command>` for details on [<args>] for a subcommand.",
error_code(2, "The blade is too dull."),
Expand Down Expand Up @@ -851,7 +967,7 @@ Options:
assert_help_string::<HelpExample>(
r###"Usage: test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s <scribble> [-v] <command> [<args>]

Destroy the contents of <file>.
Destroy the contents of <file> with a specific "method of destruction".

Options:
-f, --force force, ignore minor errors. This description is so long that
Expand All @@ -861,6 +977,7 @@ Options:
-s, --scribble write <scribble> repeatedly
-v, --verbose say more. Defaults to $BLAST_VERBOSE.
--help display usage information
--help-json display usage information encoded in JSON

Commands:
blow-up explosively separate
Expand All @@ -880,6 +997,31 @@ Error codes:
);
}

#[test]
fn help_json_example() {
assert_help_json_string::<HelpExample>(
vec!["--help-json"],
r###"{
"usage": "test_arg_0 [-f] [--really-really-really-long-name-for-pat] -s <scribble> [-v] <command> [<args>]",
"description": "Destroy the contents of <file> with a specific \"method of destruction\".",
"options": [{"short": "f", "long": "--force", "description": "force, ignore minor errors. This description is so long that it wraps to the next line."},
{"short": "", "long": "--really-really-really-long-name-for-pat", "description": "documentation"},
{"short": "s", "long": "--scribble", "description": "write <scribble> repeatedly"},
{"short": "v", "long": "--verbose", "description": "say more. Defaults to $BLAST_VERBOSE."},
{"short": "", "long": "--help", "description": "display usage information"},
{"short": "", "long": "--help-json", "description": "display usage information encoded in JSON"}],
"positional": [],
"examples": "Scribble 'abc' and then run |grind|.\n$ test_arg_0 -s 'abc' grind old.txt taxes.cp",
"notes": "Use `test_arg_0 help <command>` for details on [<args>] for a subcommand.",
"error_codes": [{"name": "2", "description": "The blade is too dull."},
{"name": "3", "description": "Out of fuel."}],
"subcommands": [{"name": "blow-up", "description": "explosively separate"},
{"name": "grind", "description": "make smaller by many small cuts"}]
}
"###,
);
}

#[allow(dead_code)]
#[derive(argh::FromArgs)]
/// Destroy the contents of <file>.
Expand All @@ -900,6 +1042,7 @@ Positional Arguments:

Options:
--help display usage information
--help-json display usage information encoded in JSON
"###,
);
}
Expand Down Expand Up @@ -1263,6 +1406,7 @@ Woot
Options:
-n, --n fooey
--help display usage information
--help-json display usage information encoded in JSON
"###
.to_string(),
status: Ok(()),
Expand Down
Loading