diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8c91e4f..5cbe355 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,6 +63,13 @@ jobs: run: python3 scripts/validate-render.py - name: Smoke test command metadata run: | + ./target/release/stack help + ./target/release/stack help render + ./target/release/stack help icons list + ./target/release/stack version + ./target/release/stack -h + ./target/release/stack -v + ./target/release/stack -V ./target/release/stack --help ./target/release/stack --version ./target/release/stack check --help diff --git a/README.md b/README.md index 2d7b461..9e589c5 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ The repository contains native validation, formatting, and rendering commands. T ## Commands ```text +stack help +stack help render +stack version stack check arch.stack stack fmt arch.stack stack fmt --check arch.stack @@ -20,6 +23,8 @@ stack icons import simple-icons --accept-terms stack render arch.stack -o arch.svg --notice arch.NOTICE.md ``` +`stack help`, `stack -h`, and `stack --help` print top-level help. Use `stack help ` or ` -h` / ` --help` for command-specific usage and examples; nested icon help is available through `stack help icons `. `stack version`, `stack -v`, `stack -V`, and `stack --version` print the same Cargo package version. Help and version output use standard output and exit with status `0`. Invalid arguments and unknown commands use standard error and status `2`; close command typos include a suggested command and the relevant help invocation. + `stack check` reads the file as bytes and runs the full compiler, theme, layout, and routing validation pipeline without changing the source. Diagnostics are written to standard error in source order. Standard output remains empty. `stack fmt` uses the engine formatter and preserves comments. File mode replaces changed source atomically through a temporary file in the same directory; unchanged files are not replaced. Syntax, encoding, and host I/O failures leave the original file untouched. `stack fmt -` reads bytes from standard input and writes only canonical source to standard output. `--check` never writes source and exits with status `1` when formatting is required. diff --git a/src/lib.rs b/src/lib.rs index 49fd8f3..52ce5dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,14 +25,182 @@ pub const EXIT_STACK_ERROR: u8 = 1; /// Exit status used for argument, host I/O, or engine operational failures. pub const EXIT_USAGE_OR_IO: u8 = 2; -const GENERAL_HELP: &str = "Stack diagram toolchain\n\nUsage:\n stack check \n stack fmt [--check] \n stack render [--provider-pack ] [-o ] [--notice ]\n stack icons list [PROVIDER] [QUERY]\n stack icons import --accept-terms [-o ]\n stack --help\n stack --version\n\nCommands:\n check Validate a Stack source file without modifying it\n fmt Format a file in place or read from standard input\n render Render standalone SVG to standard output or a file\n icons List catalogs and import audited provider icon archives\n"; -const CHECK_HELP: &str = - "Validate a Stack source file without modifying it\n\nUsage:\n stack check \n"; -const FORMAT_HELP: &str = "Format Stack source canonically\n\nUsage:\n stack fmt \n stack fmt --check \n stack fmt -\n\nArguments:\n Format the file atomically in place\n - Read from standard input and write to standard output\n\nOptions:\n --check Report whether formatting is required without writing output\n"; -const RENDER_HELP: &str = "Render Stack source as standalone SVG\n\nUsage:\n stack render [--provider-pack ] [-o ] [--notice ]\n\nArguments:\n Read Stack source bytes from this file\n\nOptions:\n --provider-pack Read known provider packs from this icon-store root\n -o Write SVG atomically instead of using standard output\n --notice Write exact used-provider notices atomically\n\nDefault icon store:\n $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons\n"; -const ICONS_HELP: &str = "Manage local provider icon packs\n\nUsage:\n stack icons list [PROVIDER] [QUERY]\n stack icons import --accept-terms [-o ]\n\nProviders:\n aws 305 AWS Architecture Icons\n gcp 45 Google Cloud product and category icons\n azure 639 Azure Public Service Icons\n simple-icons 62 curated developer and collaboration tools\n"; -const ICONS_LIST_HELP: &str = "List searchable asset-free provider catalog metadata\n\nUsage:\n stack icons list\n stack icons list [QUERY]\n\nArguments:\n aws, gcp, azure, or simple-icons\n [QUERY] Case-insensitive ID, product, or category substring\n"; -const ICONS_IMPORT_HELP: &str = "Download and import audited provider icon archives\n\nUsage:\n stack icons import --accept-terms [-o ]\n\nArguments:\n aws, gcp, azure, or simple-icons\n\nOptions:\n --accept-terms Confirm that you reviewed all provider and brand terms\n -o Store packs below this icon-store root\n\nDefault icon store:\n $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons\n"; +const GENERAL_HELP: &str = "\ +Stack diagram toolchain + +Usage: + stack [OPTIONS] + stack help [COMMAND] + +Commands: + check Validate a Stack source file without modifying it + fmt Format a file in place or read from standard input + render Render standalone SVG to standard output or a file + icons List catalogs and import audited provider icon archives + help Print this message or the help of a subcommand + version Print version information + +Options: + -h, --help Print help + -v, -V, --version Print version + +Examples: + stack check arch.stack + stack fmt --check arch.stack + stack render arch.stack -o arch.svg + stack icons list aws s3 +"; +const CHECK_HELP: &str = "\ +Validate a Stack source file without modifying it + +Usage: + stack check + +Arguments: + Read Stack source bytes from this file + +Options: + -h, --help Print help + +Examples: + stack check arch.stack +"; +const FORMAT_HELP: &str = "\ +Format Stack source canonically + +Usage: + stack fmt + stack fmt --check + stack fmt - + +Arguments: + Format the file atomically in place + - Read from standard input and write to standard output + +Options: + --check Report whether formatting is required without writing output + -h, --help Print help + +Examples: + stack fmt arch.stack + stack fmt --check arch.stack + stack fmt - < input.stack > output.stack +"; +const RENDER_HELP: &str = "\ +Render Stack source as standalone SVG + +Usage: + stack render [--provider-pack ] [-o ] [--notice ] + +Arguments: + Read Stack source bytes from this file + +Options: + --provider-pack Read known provider packs from this icon-store root + -o Write SVG atomically instead of using standard output + --notice Write exact used-provider notices atomically + -h, --help Print help + +Default icon store: + $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons + +Examples: + stack render arch.stack + stack render arch.stack -o arch.svg + stack render arch.stack --notice arch.NOTICE.md -o arch.svg +"; +const ICONS_HELP: &str = "\ +Manage local provider icon packs + +Usage: + stack icons + +Commands: + list List searchable asset-free provider catalog metadata + import Download and import an audited provider icon archive + help Print this message or the help of an icons subcommand + +Options: + -h, --help Print help + +Providers: + aws 305 AWS Architecture Icons + gcp 45 Google Cloud product and category icons + azure 639 Azure Public Service Icons + simple-icons 62 curated developer and collaboration tools + +Examples: + stack icons list + stack icons list aws s3 + stack icons import gcp --accept-terms +"; +const ICONS_LIST_HELP: &str = "\ +List searchable asset-free provider catalog metadata + +Usage: + stack icons list + stack icons list [QUERY] + +Arguments: + aws, gcp, azure, or simple-icons + [QUERY] Case-insensitive ID, product, or category substring + +Options: + -h, --help Print help + +Examples: + stack icons list + stack icons list aws s3 +"; +const ICONS_IMPORT_HELP: &str = "\ +Download and import audited provider icon archives + +Usage: + stack icons import --accept-terms [-o ] + +Arguments: + aws, gcp, azure, or simple-icons + +Options: + --accept-terms Confirm that you reviewed all provider and brand terms + -o Store packs below this icon-store root + -h, --help Print help + +Default icon store: + $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons + +Examples: + stack icons import gcp --accept-terms + stack icons import simple-icons --accept-terms -o .stack-icons +"; +const HELP_HELP: &str = "\ +Print top-level or subcommand help + +Usage: + stack help + stack help + stack help icons + +Arguments: + check, fmt, render, icons, help, or version + +Examples: + stack help + stack help render + stack help icons import +"; +const VERSION_HELP: &str = "\ +Print Stack CLI version information + +Usage: + stack version + +Options: + -h, --help Print help + +Examples: + stack version +"; const MAX_PROVIDER_MANIFEST_BYTES: usize = 1024 * 1024; const MAX_PROVIDER_ASSET_BYTES: usize = 1024 * 1024; @@ -62,7 +230,7 @@ pub fn run( return argument_error("missing command", stderr); }; - if command == OsStr::new("--help") || command == OsStr::new("-h") { + if is_help_flag(&command) { if let Some(extra) = arguments.next() { return argument_error( &format!("unexpected argument '{}'", extra.to_string_lossy()), @@ -71,18 +239,20 @@ pub fn run( } return write_stdout(GENERAL_HELP, stdout, stderr); } - if command == OsStr::new("--version") || command == OsStr::new("-V") { + if is_version_flag(&command) { if let Some(extra) = arguments.next() { return argument_error( &format!("unexpected argument '{}'", extra.to_string_lossy()), stderr, ); } - return write_stdout( - concat!("stack ", env!("CARGO_PKG_VERSION"), "\n"), - stdout, - stderr, - ); + return write_version(stdout, stderr); + } + if command == OsStr::new("help") { + return run_help(arguments, stdout, stderr); + } + if command == OsStr::new("version") { + return run_version(arguments, stdout, stderr); } if command == OsStr::new("check") { return run_check(arguments, stdout, stderr); @@ -97,8 +267,84 @@ pub fn run( return run_icons(&mut arguments, stdout, stderr); } + unknown_command_error( + "stack", + &command, + &["check", "fmt", "render", "icons", "help", "version"], + "stack help", + stderr, + ) +} + +fn run_help( + mut arguments: impl Iterator, + stdout: &mut dyn Write, + stderr: &mut dyn Write, +) -> u8 { + let Some(command) = arguments.next() else { + return write_stdout(GENERAL_HELP, stdout, stderr); + }; + if is_help_flag(&command) { + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + return write_stdout(HELP_HELP, stdout, stderr); + } + if command == OsStr::new("icons") { + return run_icons_help(&mut arguments, stdout, stderr); + } + + let help = if command == OsStr::new("check") { + CHECK_HELP + } else if command == OsStr::new("fmt") { + FORMAT_HELP + } else if command == OsStr::new("render") { + RENDER_HELP + } else if command == OsStr::new("help") { + HELP_HELP + } else if command == OsStr::new("version") { + VERSION_HELP + } else { + return unknown_command_error( + "stack help", + &command, + &["check", "fmt", "render", "icons", "help", "version"], + "stack help", + stderr, + ); + }; + + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + write_stdout(help, stdout, stderr) +} + +fn run_version( + mut arguments: impl Iterator, + stdout: &mut dyn Write, + stderr: &mut dyn Write, +) -> u8 { + let Some(argument) = arguments.next() else { + return write_version(stdout, stderr); + }; + if is_help_flag(&argument) { + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + return write_stdout(VERSION_HELP, stdout, stderr); + } argument_error( - &format!("unknown command '{}'", command.to_string_lossy()), + &format!("unexpected argument '{}'", argument.to_string_lossy()), stderr, ) } @@ -111,7 +357,7 @@ fn run_icons( let Some(command) = arguments.next() else { return argument_error("missing command for 'stack icons'", stderr); }; - if command == OsStr::new("--help") || command == OsStr::new("-h") { + if is_help_flag(&command) { if let Some(extra) = arguments.next() { return argument_error( &format!("unexpected argument '{}'", extra.to_string_lossy()), @@ -120,21 +366,72 @@ fn run_icons( } return write_stdout(ICONS_HELP, stdout, stderr); } + if command == OsStr::new("help") { + return run_icons_help(arguments, stdout, stderr); + } if command == OsStr::new("list") { return run_icons_list(arguments, stdout, stderr); } if command != OsStr::new("import") { - return argument_error( - &format!( - "unknown command for 'stack icons': '{}'", - command.to_string_lossy() - ), + return unknown_command_error( + "stack icons", + &command, + &["list", "import", "help"], + "stack help icons", stderr, ); } run_icons_import(arguments, stdout, stderr) } +fn run_icons_help( + arguments: &mut dyn Iterator, + stdout: &mut dyn Write, + stderr: &mut dyn Write, +) -> u8 { + let Some(command) = arguments.next() else { + return write_stdout(ICONS_HELP, stdout, stderr); + }; + if is_help_flag(&command) { + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + return write_stdout(ICONS_HELP, stdout, stderr); + } + if command == OsStr::new("help") { + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + return write_stdout(ICONS_HELP, stdout, stderr); + } + let help = if command == OsStr::new("list") { + ICONS_LIST_HELP + } else if command == OsStr::new("import") { + ICONS_IMPORT_HELP + } else { + return unknown_command_error( + "stack icons", + &command, + &["list", "import", "help"], + "stack help icons", + stderr, + ); + }; + if let Some(extra) = arguments.next() { + return argument_error( + &format!("unexpected argument '{}'", extra.to_string_lossy()), + stderr, + ); + } + write_stdout(help, stdout, stderr) +} + fn run_icons_list( arguments: &mut dyn Iterator, stdout: &mut dyn Write, @@ -1065,6 +1362,72 @@ fn stable_io_error(kind: io::ErrorKind) -> &'static str { } } +fn is_help_flag(value: &OsStr) -> bool { + value == OsStr::new("--help") || value == OsStr::new("-h") +} + +fn is_version_flag(value: &OsStr) -> bool { + value == OsStr::new("--version") || value == OsStr::new("-V") || value == OsStr::new("-v") +} + +fn write_version(stdout: &mut dyn Write, stderr: &mut dyn Write) -> u8 { + write_stdout( + concat!("stack ", env!("CARGO_PKG_VERSION"), "\n"), + stdout, + stderr, + ) +} + +fn unknown_command_error( + scope: &str, + command: &OsStr, + candidates: &[&str], + help_command: &str, + stderr: &mut dyn Write, +) -> u8 { + let command = command.to_string_lossy(); + if scope == "stack" { + let _ = writeln!(stderr, "error: unknown command '{command}'"); + } else { + let _ = writeln!(stderr, "error: unknown command for '{scope}': '{command}'"); + } + if let Some(suggestion) = command_suggestion(&command, candidates) { + let _ = writeln!(stderr, "\nDid you mean '{suggestion}'?"); + } + let _ = writeln!(stderr, "\nFor more information, try '{help_command}'."); + EXIT_USAGE_OR_IO +} + +fn command_suggestion<'a>(input: &str, candidates: &[&'a str]) -> Option<&'a str> { + let input_length = input.chars().count(); + candidates + .iter() + .map(|candidate| (*candidate, edit_distance(input, candidate))) + .filter(|(candidate, distance)| { + *distance <= 2 && *distance * 2 <= input_length.max(candidate.chars().count()) + }) + .min_by_key(|(_, distance)| *distance) + .map(|(candidate, _)| candidate) +} + +fn edit_distance(left: &str, right: &str) -> usize { + let right = right.chars().collect::>(); + let mut previous = (0..=right.len()).collect::>(); + for (left_index, left_character) in left.chars().enumerate() { + let mut current = Vec::with_capacity(right.len() + 1); + current.push(left_index + 1); + for (right_index, right_character) in right.iter().enumerate() { + let substitution = + previous[right_index] + usize::from(left_character != *right_character); + let insertion = current[right_index] + 1; + let deletion = previous[right_index + 1] + 1; + current.push(substitution.min(insertion).min(deletion)); + } + previous = current; + } + previous[right.len()] +} + fn argument_error(message: &str, stderr: &mut dyn Write) -> u8 { let _ = writeln!(stderr, "error: {message}\n\n{GENERAL_HELP}"); EXIT_USAGE_OR_IO diff --git a/tests/cli.rs b/tests/cli.rs index 97927fe..88b164b 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -74,6 +74,14 @@ fn assert_unchanged(path: &Path, expected: &[u8]) -> Result<(), Box> Ok(()) } +fn assert_stdout_only(arguments: &[&str], expected: &[u8]) -> Result<(), Box> { + let output = stack(arguments.iter().copied())?; + assert_eq!(output.status.code(), Some(0), "arguments: {arguments:?}"); + assert_eq!(output.stdout, expected, "arguments: {arguments:?}"); + assert!(output.stderr.is_empty(), "arguments: {arguments:?}"); + Ok(()) +} + #[test] fn valid_source_is_silent_and_unchanged() -> Result<(), Box> { let directory = TestDirectory::new("valid")?; @@ -145,21 +153,122 @@ fn missing_file_exits_two_with_a_stable_host_error() -> Result<(), Box Result<(), Box> { - let help = stack(["--help"])?; - assert_eq!(help.status.code(), Some(0)); - assert!(help.stderr.is_empty()); - assert!(String::from_utf8(help.stdout)?.contains("stack check ")); - - let icons_help = stack(["icons", "--help"])?; - assert_eq!(icons_help.status.code(), Some(0)); - assert!(icons_help.stderr.is_empty()); - assert!(String::from_utf8(icons_help.stdout)?.contains("AWS Architecture Icons")); - - let version = stack(["--version"])?; - assert_eq!(version.status.code(), Some(0)); - assert_eq!(version.stdout, b"stack 0.3.0\n"); - assert!(version.stderr.is_empty()); +fn help_snapshots_and_aliases_are_stdout_only() -> Result<(), Box> { + let cases: &[(&[&str], &[u8])] = &[ + (&["--help"], include_bytes!("snapshots/help.txt")), + (&["-h"], include_bytes!("snapshots/help.txt")), + (&["help"], include_bytes!("snapshots/help.txt")), + ( + &["check", "--help"], + include_bytes!("snapshots/check-help.txt"), + ), + ( + &["help", "check"], + include_bytes!("snapshots/check-help.txt"), + ), + (&["fmt", "--help"], include_bytes!("snapshots/fmt-help.txt")), + (&["help", "fmt"], include_bytes!("snapshots/fmt-help.txt")), + ( + &["render", "--help"], + include_bytes!("snapshots/render-help.txt"), + ), + ( + &["help", "render"], + include_bytes!("snapshots/render-help.txt"), + ), + ( + &["icons", "--help"], + include_bytes!("snapshots/icons-help.txt"), + ), + ( + &["icons", "help"], + include_bytes!("snapshots/icons-help.txt"), + ), + ( + &["help", "icons"], + include_bytes!("snapshots/icons-help.txt"), + ), + ( + &["icons", "list", "--help"], + include_bytes!("snapshots/icons-list-help.txt"), + ), + ( + &["icons", "help", "list"], + include_bytes!("snapshots/icons-list-help.txt"), + ), + ( + &["help", "icons", "list"], + include_bytes!("snapshots/icons-list-help.txt"), + ), + ( + &["icons", "import", "--help"], + include_bytes!("snapshots/icons-import-help.txt"), + ), + ( + &["icons", "help", "import"], + include_bytes!("snapshots/icons-import-help.txt"), + ), + ( + &["help", "icons", "import"], + include_bytes!("snapshots/icons-import-help.txt"), + ), + (&["help", "help"], include_bytes!("snapshots/help-help.txt")), + ( + &["help", "--help"], + include_bytes!("snapshots/help-help.txt"), + ), + ( + &["version", "--help"], + include_bytes!("snapshots/version-help.txt"), + ), + ( + &["help", "version"], + include_bytes!("snapshots/version-help.txt"), + ), + ]; + + for (arguments, expected) in cases { + assert_stdout_only(arguments, expected)?; + } + + let expected_version = format!("stack {}\n", env!("CARGO_PKG_VERSION")); + for arguments in [["version"], ["-v"], ["-V"], ["--version"]] { + assert_stdout_only(&arguments, expected_version.as_bytes())?; + } + Ok(()) +} + +#[test] +fn command_typos_are_actionable_and_stderr_only() -> Result<(), Box> { + let cases: &[(&[&str], &str)] = &[ + ( + &["chekc"], + "error: unknown command 'chekc'\n\nDid you mean 'check'?\n\nFor more information, try 'stack help'.\n", + ), + ( + &["icons", "lst"], + "error: unknown command for 'stack icons': 'lst'\n\nDid you mean 'list'?\n\nFor more information, try 'stack help icons'.\n", + ), + ( + &["help", "rennder"], + "error: unknown command for 'stack help': 'rennder'\n\nDid you mean 'render'?\n\nFor more information, try 'stack help'.\n", + ), + ( + &["definitely-unknown"], + "error: unknown command 'definitely-unknown'\n\nFor more information, try 'stack help'.\n", + ), + ]; + + for (arguments, expected) in cases { + let output = stack(arguments.iter().copied())?; + assert_eq!(output.status.code(), Some(2), "arguments: {arguments:?}"); + assert!(output.stdout.is_empty(), "arguments: {arguments:?}"); + assert_eq!( + output.stderr, + expected.as_bytes(), + "arguments: {arguments:?}" + ); + } Ok(()) } diff --git a/tests/snapshots/check-help.txt b/tests/snapshots/check-help.txt new file mode 100644 index 0000000..69fd7e2 --- /dev/null +++ b/tests/snapshots/check-help.txt @@ -0,0 +1,13 @@ +Validate a Stack source file without modifying it + +Usage: + stack check + +Arguments: + Read Stack source bytes from this file + +Options: + -h, --help Print help + +Examples: + stack check arch.stack diff --git a/tests/snapshots/fmt-help.txt b/tests/snapshots/fmt-help.txt new file mode 100644 index 0000000..d555488 --- /dev/null +++ b/tests/snapshots/fmt-help.txt @@ -0,0 +1,19 @@ +Format Stack source canonically + +Usage: + stack fmt + stack fmt --check + stack fmt - + +Arguments: + Format the file atomically in place + - Read from standard input and write to standard output + +Options: + --check Report whether formatting is required without writing output + -h, --help Print help + +Examples: + stack fmt arch.stack + stack fmt --check arch.stack + stack fmt - < input.stack > output.stack diff --git a/tests/snapshots/help-help.txt b/tests/snapshots/help-help.txt new file mode 100644 index 0000000..7fc68f3 --- /dev/null +++ b/tests/snapshots/help-help.txt @@ -0,0 +1,14 @@ +Print top-level or subcommand help + +Usage: + stack help + stack help + stack help icons + +Arguments: + check, fmt, render, icons, help, or version + +Examples: + stack help + stack help render + stack help icons import diff --git a/tests/snapshots/help.txt b/tests/snapshots/help.txt new file mode 100644 index 0000000..7c41c6a --- /dev/null +++ b/tests/snapshots/help.txt @@ -0,0 +1,23 @@ +Stack diagram toolchain + +Usage: + stack [OPTIONS] + stack help [COMMAND] + +Commands: + check Validate a Stack source file without modifying it + fmt Format a file in place or read from standard input + render Render standalone SVG to standard output or a file + icons List catalogs and import audited provider icon archives + help Print this message or the help of a subcommand + version Print version information + +Options: + -h, --help Print help + -v, -V, --version Print version + +Examples: + stack check arch.stack + stack fmt --check arch.stack + stack render arch.stack -o arch.svg + stack icons list aws s3 diff --git a/tests/snapshots/icons-help.txt b/tests/snapshots/icons-help.txt new file mode 100644 index 0000000..b54294f --- /dev/null +++ b/tests/snapshots/icons-help.txt @@ -0,0 +1,23 @@ +Manage local provider icon packs + +Usage: + stack icons + +Commands: + list List searchable asset-free provider catalog metadata + import Download and import an audited provider icon archive + help Print this message or the help of an icons subcommand + +Options: + -h, --help Print help + +Providers: + aws 305 AWS Architecture Icons + gcp 45 Google Cloud product and category icons + azure 639 Azure Public Service Icons + simple-icons 62 curated developer and collaboration tools + +Examples: + stack icons list + stack icons list aws s3 + stack icons import gcp --accept-terms diff --git a/tests/snapshots/icons-import-help.txt b/tests/snapshots/icons-import-help.txt new file mode 100644 index 0000000..7ebeffd --- /dev/null +++ b/tests/snapshots/icons-import-help.txt @@ -0,0 +1,19 @@ +Download and import audited provider icon archives + +Usage: + stack icons import --accept-terms [-o ] + +Arguments: + aws, gcp, azure, or simple-icons + +Options: + --accept-terms Confirm that you reviewed all provider and brand terms + -o Store packs below this icon-store root + -h, --help Print help + +Default icon store: + $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons + +Examples: + stack icons import gcp --accept-terms + stack icons import simple-icons --accept-terms -o .stack-icons diff --git a/tests/snapshots/icons-list-help.txt b/tests/snapshots/icons-list-help.txt new file mode 100644 index 0000000..d67a50f --- /dev/null +++ b/tests/snapshots/icons-list-help.txt @@ -0,0 +1,16 @@ +List searchable asset-free provider catalog metadata + +Usage: + stack icons list + stack icons list [QUERY] + +Arguments: + aws, gcp, azure, or simple-icons + [QUERY] Case-insensitive ID, product, or category substring + +Options: + -h, --help Print help + +Examples: + stack icons list + stack icons list aws s3 diff --git a/tests/snapshots/render-help.txt b/tests/snapshots/render-help.txt new file mode 100644 index 0000000..a0f71f7 --- /dev/null +++ b/tests/snapshots/render-help.txt @@ -0,0 +1,21 @@ +Render Stack source as standalone SVG + +Usage: + stack render [--provider-pack ] [-o ] [--notice ] + +Arguments: + Read Stack source bytes from this file + +Options: + --provider-pack Read known provider packs from this icon-store root + -o Write SVG atomically instead of using standard output + --notice Write exact used-provider notices atomically + -h, --help Print help + +Default icon store: + $XDG_CONFIG_HOME/stack/icons or $HOME/.config/stack/icons + +Examples: + stack render arch.stack + stack render arch.stack -o arch.svg + stack render arch.stack --notice arch.NOTICE.md -o arch.svg diff --git a/tests/snapshots/version-help.txt b/tests/snapshots/version-help.txt new file mode 100644 index 0000000..5f0c392 --- /dev/null +++ b/tests/snapshots/version-help.txt @@ -0,0 +1,10 @@ +Print Stack CLI version information + +Usage: + stack version + +Options: + -h, --help Print help + +Examples: + stack version