Skip to content

Commit

Permalink
feat: add comment syntax for file scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Sep 19, 2024
1 parent a67de2e commit ee75493
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 14 deletions.
56 changes: 56 additions & 0 deletions cli/src/cli/bash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::fmt::Debug;
use std::path::PathBuf;
use std::process::Stdio;

use clap::Args;
use heck::ToSnakeCase;
use itertools::Itertools;
use miette::IntoDiagnostic;

use usage::cli::ParseValue;
use usage::Spec;

#[derive(Debug, Args)]
#[clap()]
pub struct Bash {
script: PathBuf,
/// arguments to pass to script
#[clap(allow_hyphen_values = true)]
args: Vec<String>,
}

impl Bash {
pub fn run(&mut self) -> miette::Result<()> {
let (spec, _script) = Spec::parse_file(&self.script)?;
let mut args = self.args.clone();
args.insert(0, spec.bin.clone());
let parsed = usage::cli::parse(&spec, &args)?;

let mut cmd = std::process::Command::new("bash");
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
// TODO: set positional args

let args = vec![self.script.to_str().unwrap().to_string()]
.into_iter()
.chain(self.args.clone())
.collect_vec();
cmd.args(&args);

for (flag, val) in &parsed.flags {
let key = format!("usage_{}", flag.name.to_snake_case());
let val = match val {
ParseValue::Bool(b) => if *b { "1" } else { "0" }.to_string(),
ParseValue::String(s) => s.clone(),
ParseValue::MultiBool(b) => b.iter().map(|b| if *b { "1" } else { "0" }).join(","),
ParseValue::MultiString(_s) => unimplemented!("multi string"),
};
cmd.env(key, val);
}

cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;

Ok(())
}
}
3 changes: 3 additions & 0 deletions cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::{Parser, Subcommand};
use miette::Result;

mod bash;
mod complete_word;
mod exec;
mod generate;
Expand All @@ -14,6 +15,7 @@ pub struct Cli {

#[derive(Subcommand)]
enum Command {
Bash(bash::Bash),
CompleteWord(complete_word::CompleteWord),
Exec(exec::Exec),
Generate(generate::Generate),
Expand All @@ -23,6 +25,7 @@ impl Cli {
pub fn run(argv: &[String]) -> Result<()> {
let cli = Self::parse_from(argv);
match cli.command {
Command::Bash(mut cmd) => cmd.run(),
Command::Generate(cmd) => cmd.run(),
Command::Exec(mut cmd) => cmd.run(),
Command::CompleteWord(cmd) => cmd.run(),
Expand Down
14 changes: 7 additions & 7 deletions docs/cli/scripts.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Usage Scripts

Scripts can be used with the Usage CLI to display help, powerful arg parsing, and autocompletion in any language.
The Usage CLI can be used with "double-shebang" scripts which contain both the Usage definition and script in a
single file. Here is an example in bash:
Here is an example in bash:

```bash
#!/usr/bin/env usage
flag "-f --force" help="Overwrite existing <file>"
flag "-u --user <user>" help="User to run as"
arg "<file>" help="The file to write" default="file.txt"
#!/usr/bin/env -S usage bash
# |usage.jdx.dev|
# flag "-f --force" help="Overwrite existing <file>"
# flag "-u --user <user>" help="User to run as"
# arg "<file>" help="The file to write" default="file.txt"
# |usage.jdx.dev|

#!/usr/bin/env bash
if [ "$usage_force" = "true" ]; then
rm -f "$usage_file"
fi
Expand Down
14 changes: 7 additions & 7 deletions examples/example.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env usage
#!/usr/bin/env -S usage bash
# |usage.jdx.dev|
# bin "ex"
# flag "--foo" help="Flag value"
# flag "--bar <bar>" help="Option value"
# arg "baz" help="Positional values"
# |usage.jdx.dev|
# shellcheck disable=all
bin "ex"
flag "--foo" help="Flag value"
flag "--bar <bar>" help="Option value"
arg "baz" help="Positional values"

#!/usr/bin/env bash
set -euo pipefail

echo foo: $usage_foo
Expand Down
18 changes: 18 additions & 0 deletions lib/src/parse/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ impl Spec {

fn split_script(file: &Path) -> Result<(String, String), UsageErr> {
let full = file::read_to_string(file)?;
if full.contains("# |usage.jdx.dev|") {
return Ok((extract_usage_from_comments(&full), full));
}
let schema = full.strip_prefix("#!/usr/bin/env usage\n").unwrap_or(&full);
let (schema, body) = schema.split_once("\n#!").unwrap_or((schema, ""));
let schema = schema
Expand All @@ -144,6 +147,21 @@ fn split_script(file: &Path) -> Result<(String, String), UsageErr> {
Ok((schema, body))
}

fn extract_usage_from_comments(full: &str) -> String {
let mut usage = vec![];
let mut inside = false;
for line in full.lines() {
if line.starts_with("# |usage.jdx.dev|") {
inside = !inside;
continue;
}
if inside {
usage.push(line.strip_prefix("# ").unwrap());
}
}
usage.join("\n")
}

fn set_subcommand_ancestors(cmd: &mut SpecCommand, ancestors: &[String]) {
if cmd.usage.is_empty() {
cmd.usage = cmd.usage();
Expand Down

0 comments on commit ee75493

Please sign in to comment.