Skip to content

Commit

Permalink
feat: Add usage validation
Browse files Browse the repository at this point in the history
  • Loading branch information
juanibiapina committed May 22, 2024
1 parent 4078946 commit 499c4e6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 1 deletion.
1 change: 1 addition & 0 deletions integration/fixtures/v1/libexec/invalid-usage
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Usage:
14 changes: 14 additions & 0 deletions integration/validate.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bats
load test_helper

PROJECT_DIR="$SUB_TEST_DIR/v1"

@test "sub: validates all subcommands in the project directory" {
fixture "v1"

run $SUB_BIN --name main --absolute "$PROJECT_DIR" --validate

assert_failure
assert_output "$PROJECT_DIR/libexec/invalid-usage: invalid usage string
found end of input but expected \"{\""
}
10 changes: 10 additions & 0 deletions src/commands/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,14 @@ impl<'a> Command for DirectoryCommand<'a> {

Ok(0)
}

fn validate(&self) -> Vec<(PathBuf, Error)> {
let mut errors = Vec::new();

for subcommand in self.subcommands() {
errors.extend(subcommand.validate());
}

errors
}
}
7 changes: 7 additions & 0 deletions src/commands/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,11 @@ impl<'a> Command for FileCommand<'a> {
None => Err(Error::SubCommandInterrupted),
}
}

fn validate(&self) -> Vec<(PathBuf, Error)> {
match self.usage.validate() {
Ok(_) => Vec::new(),
Err(e) => vec![(self.path.clone(), e)],
}
}
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod file;
pub mod directory;

use std::path::PathBuf;
use std::os::unix::fs::PermissionsExt;

use crate::config::Config;
Expand All @@ -17,6 +18,7 @@ pub trait Command {
fn completions(&self) -> Result<i32>;
fn invoke(&self) -> Result<i32>;
fn help(&self) -> Result<String>;
fn validate(&self) -> Vec<(PathBuf, Error)>;
}

pub fn subcommand(config: &Config, mut cliargs: Vec<String>) -> Result<Box<dyn Command + '_>> {
Expand Down
51 changes: 50 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ extern crate sub;

extern crate clap;

use clap::{value_parser, Arg, ArgGroup, Command};
use clap::{value_parser, Arg, ArgAction, ArgGroup, Command};

use std::path::{Path, PathBuf};
use std::process::exit;
Expand All @@ -16,6 +16,28 @@ fn main() {

let config = Config::new(sub_cli_args.name, sub_cli_args.root, sub_cli_args.color, sub_cli_args.infer_long_arguments);

if sub_cli_args.validate {
let top_level_command = match subcommand(&config, Vec::new()) {
Ok(subcommand) => subcommand,
Err(error) => handle_error(
&config,
error,
false,
),
};

let errors = top_level_command.validate();
for error in &errors {
println!("{}: {}", error.0.display(), print_error(error.1.clone()));
}

if errors.is_empty() {
exit(0);
} else {
exit(1);
}
}

let user_cli_command = config.user_cli_command(&config.name);
let user_cli_args = parse_user_cli_args(&user_cli_command, sub_cli_args.cliargs);

Expand Down Expand Up @@ -69,6 +91,23 @@ fn main() {
}
}

fn print_error(error: Error) -> String {
match error {
Error::NoCompletions => "no completions".to_string(),
Error::SubCommandInterrupted => "sub command interrupted".to_string(),
Error::NonExecutable(_) => "non-executable".to_string(),
Error::UnknownSubCommand(name) => format!("unknown sub command '{}'", name),
Error::InvalidUsageString(errors) => {
let mut message = "invalid usage string".to_string();
for error in errors {
message.push_str(&format!("\n {}", error));
}
message
}
Error::InvalidUTF8 => "invalid UTF-8".to_string(),
}
}

fn handle_error(config: &Config, error: Error, silent: bool) -> ! {
match error {
Error::NoCompletions => exit(1),
Expand Down Expand Up @@ -142,6 +181,13 @@ fn init_sub_cli() -> Command {
.value_parser(absolute_path)
.help("Sets how to find the root directory as an absolute path"),
)
.arg(
Arg::new("validate")
.long("validate")
.num_args(0)
.action(ArgAction::SetTrue)
.help("Validate that the CLI is correctly configured"),
)
.group(
ArgGroup::new("path")
.args(["bin", "absolute"])
Expand All @@ -160,6 +206,7 @@ struct SubCliArgs {
color: Color,
root: PathBuf,
infer_long_arguments: bool,
validate: bool,
cliargs: Vec<String>,
}

Expand Down Expand Up @@ -233,6 +280,8 @@ fn parse_sub_cli_args() -> SubCliArgs {

infer_long_arguments: args.get_one::<bool>("infer-long-arguments").cloned().unwrap_or(false),

validate: args.get_flag("validate"),

cliargs: args
.get_many("cliargs")
.map(|cmds| cmds.cloned().collect::<Vec<_>>())
Expand Down

0 comments on commit 499c4e6

Please sign in to comment.