use crate::assert_help_snapshot;
use facet::Facet;
use figue as args;
use figue::FigueBuiltins;
#[derive(Facet, Debug)]
struct SimpleArgs {
#[facet(args::named, args::short = 'v')]
verbose: bool,
#[facet(args::named, args::short = 'j', args::label = "count")]
jobs: Option<usize>,
#[facet(args::positional)]
input: String,
#[facet(default, args::positional)]
output: Option<String>,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[test]
fn test_help_simple_struct() {
let config = figue::HelpConfig {
program_name: Some("myapp".to_string()),
version: Some("1.0.0".to_string()),
..Default::default()
};
let help = figue::generate_help::<SimpleArgs>(&config);
assert_help_snapshot!(help);
}
#[derive(Facet, Debug)]
struct GitArgs {
#[facet(args::subcommand)]
command: GitCommand,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum GitCommand {
Clone {
#[facet(args::positional)]
url: String,
#[facet(default, args::positional)]
directory: Option<String>,
},
Log {
#[facet(args::named, args::short = 'n')]
count: Option<usize>,
#[facet(args::named)]
oneline: bool,
},
Remote {
#[facet(args::subcommand)]
action: RemoteAction,
},
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum RemoteAction {
Add {
#[facet(args::positional)]
name: String,
#[facet(args::positional)]
url: String,
},
#[facet(rename = "rm")]
Remove {
#[facet(args::positional)]
name: String,
},
#[facet(rename = "ls")]
List {
#[facet(args::named, args::short = 'v')]
verbose: bool,
},
}
#[test]
fn test_help_with_subcommands() {
let config = figue::HelpConfig {
program_name: Some("git".to_string()),
version: Some("2.40.0".to_string()),
..Default::default()
};
let help = figue::generate_help::<GitArgs>(&config);
assert_help_snapshot!(help);
}
#[test]
fn test_help_enum_only() {
let config = figue::HelpConfig {
program_name: Some("git".to_string()),
..Default::default()
};
let help = figue::generate_help::<GitCommand>(&config);
assert_help_snapshot!(help);
}
#[test]
fn test_help_flags() {
let result = figue::from_slice::<SimpleArgs>(&["--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help());
assert!(err.help_text().is_some());
let help = err.help_text().unwrap();
assert!(help.contains("USAGE"));
assert!(help.contains("--verbose"));
let result = figue::from_slice::<SimpleArgs>(&["-h"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help());
}
#[test]
fn test_tuple_variant_subcommand_help_flattening() {
#[derive(Facet, Debug)]
struct BuildArgs {
#[facet(args::named, args::short = 'r')]
release: bool,
#[facet(args::named)]
no_spawn: bool,
#[facet(args::named)]
no_tui: bool,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum Command {
Build(BuildArgs),
Test {
#[facet(args::named, args::short = 'v')]
verbose: bool,
},
}
#[derive(Facet, Debug)]
struct Args {
#[facet(args::subcommand)]
command: Command,
#[facet(flatten)]
builtins: FigueBuiltins,
}
let config = figue::HelpConfig {
program_name: Some("myapp".to_string()),
..Default::default()
};
let help = figue::generate_help::<Args>(&config);
assert_help_snapshot!("tuple_variant_main_help", help);
}
#[derive(Facet, Debug)]
struct PkgManager {
#[facet(args::subcommand)]
command: PkgCommand,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum PkgCommand {
Install {
#[facet(args::positional)]
package: String,
#[facet(args::named, args::short = 'g')]
global: bool,
#[facet(args::named, args::short = 'f')]
force: bool,
},
#[facet(rename = "rm")]
Remove {
#[facet(args::positional)]
package: String,
#[facet(args::named, args::short = 'y')]
yes: bool,
},
#[facet(rename = "ls")]
List {
#[facet(args::named, args::short = 'a')]
all: bool,
#[facet(args::named)]
json: bool,
},
}
#[test]
fn test_help_subcommand_install() {
let result = figue::from_slice::<PkgManager>(&["install", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(help.contains("install"), "help should mention 'install'");
assert!(
help.contains("PACKAGE"),
"help should show PACKAGE positional"
);
assert!(
help.contains("--global") || help.contains("-g"),
"help should show --global flag"
);
assert!(
help.contains("--force") || help.contains("-f"),
"help should show --force flag"
);
assert!(
!help.contains("--yes"),
"help should not show --yes from remove"
);
assert!(
!help.contains("--json"),
"help should not show --json from list"
);
assert_help_snapshot!("subcommand_install_help", help);
}
#[test]
fn test_help_subcommand_remove() {
let result = figue::from_slice::<PkgManager>(&["rm", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(
help.contains("rm") || help.contains("remove"),
"help should mention 'rm' or 'remove'"
);
assert!(
help.contains("PACKAGE"),
"help should show PACKAGE positional"
);
assert!(
help.contains("--yes") || help.contains("-y"),
"help should show --yes flag"
);
assert!(
!help.contains("--global"),
"help should not show --global from install"
);
assert!(
!help.contains("--json"),
"help should not show --json from list"
);
assert_help_snapshot!("subcommand_remove_help", help);
}
#[test]
fn test_help_subcommand_list() {
let result = figue::from_slice::<PkgManager>(&["ls", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(
help.contains("ls") || help.contains("list"),
"help should mention 'ls' or 'list'"
);
assert!(
help.contains("--all") || help.contains("-a"),
"help should show --all flag"
);
assert!(help.contains("--json"), "help should show --json flag");
assert!(
!help.contains("--global"),
"help should not show --global from install"
);
assert!(
!help.contains("--yes"),
"help should not show --yes from remove"
);
assert_help_snapshot!("subcommand_list_help", help);
}
#[test]
fn test_help_root_shows_all_subcommands() {
let result = figue::from_slice::<PkgManager>(&["--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(help.contains("install"), "root help should list install");
assert!(
help.contains("rm"),
"root help should list rm (renamed from remove)"
);
assert!(
help.contains("ls"),
"root help should list ls (renamed from list)"
);
assert_help_snapshot!("subcommand_root_help", help);
}
#[derive(Facet, Debug)]
struct NestedCli {
#[facet(args::subcommand)]
command: TopLevel,
#[facet(flatten)]
builtins: FigueBuiltins,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum TopLevel {
Repo {
#[facet(args::subcommand)]
action: RepoCmd,
},
Version,
}
#[derive(Facet, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum RepoCmd {
Clone {
#[facet(args::positional)]
url: String,
#[facet(args::named)]
depth: Option<u32>,
#[facet(args::named, args::short = 'b')]
branch: Option<String>,
},
Push {
#[facet(args::positional, default)]
remote: Option<String>,
#[facet(args::named, args::short = 'f')]
force: bool,
},
}
#[test]
fn test_help_nested_subcommand_clone() {
let result = figue::from_slice::<NestedCli>(&["repo", "clone", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(help.contains("clone"), "help should mention 'clone'");
assert!(help.contains("URL"), "help should show URL positional");
assert!(help.contains("--depth"), "help should show --depth flag");
assert!(
help.contains("--branch") || help.contains("-b"),
"help should show --branch flag"
);
assert!(
!help.contains("--force"),
"help should not show --force from push"
);
assert!(
help.contains("repo") && help.contains("clone"),
"usage should show full subcommand path"
);
assert_help_snapshot!("nested_subcommand_clone_help", help);
}
#[test]
fn test_help_nested_subcommand_push() {
let result = figue::from_slice::<NestedCli>(&["repo", "push", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(help.contains("push"), "help should mention 'push'");
assert!(
help.contains("--force") || help.contains("-f"),
"help should show --force flag"
);
assert!(
!help.contains("--depth"),
"help should not show --depth from clone"
);
assert!(
!help.contains("--branch"),
"help should not show --branch from clone"
);
assert_help_snapshot!("nested_subcommand_push_help", help);
}
#[test]
fn test_help_nested_intermediate_level() {
let result = figue::from_slice::<NestedCli>(&["repo", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_help(), "expected help error, got: {:?}", err);
let help = err.help_text().expect("should have help text");
assert!(help.contains("clone"), "help should list clone subcommand");
assert!(help.contains("push"), "help should list push subcommand");
assert!(help.contains("repo"), "usage should show 'repo'");
assert_help_snapshot!("nested_intermediate_repo_help", help);
}
#[test]
fn test_help_short_flag_h_works_in_subcommand() {
let result = figue::from_slice::<PkgManager>(&["install", "-h"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.is_help(),
"expected help error for -h flag, got: {:?}",
err
);
let help = err.help_text().expect("should have help text");
assert!(
help.contains("--global") || help.contains("-g"),
"help should show --global flag (install-specific)"
);
assert!(
help.contains("--force") || help.contains("-f"),
"help should show --force flag (install-specific)"
);
assert!(
help.contains("PACKAGE"),
"help should show PACKAGE positional"
);
assert!(
!help.contains("--yes"),
"help should not show --yes from remove"
);
assert!(
!help.contains("--json"),
"help should not show --json from list"
);
}