1use super::SourceOptions;
3
4pub const USAGE: &str = "$fy - Convert shell scripts and competitor source to command-stream modules\n\nUsage: $fy [options] [input.sh|input.mjs|input.cjs|-] [output.mjs]\nRead stdin when no input is given or input is -; print unless output is given.\n\n --from shell|javascript|zx|execa|shelljs|cross-spawn|bun\n --shell sh|bash|dash|zsh Interpreter (default: shebang or sh)\n --module NAME Target package (default: command-stream)\n --no-use-m Import the installed package directly\n --no-shebang Omit the generated interpreter line\n --commonjs Wrap CommonJS source as an ES module\n --structural Compile the supported shell subset to JS statements\n -h, --help Show this help\n\nShell programs use command-stream + use-m and retain shell semantics.\nJavaScript imports migrate to command-stream compatibility APIs.\nOutput files must not already exist.\n";
5
6#[derive(Default)]
7pub struct Arguments {
8 pub options: SourceOptions,
9 pub positional: Vec<String>,
10 pub structural: bool,
11 pub help: bool,
12}
13
14pub fn parse_arguments(args: &[String]) -> Result<Arguments, String> {
15 let mut result = Arguments::default();
16 let mut args = args.iter();
17 while let Some(arg) = args.next() {
18 match arg.as_str() {
19 "--" => {
20 result.positional.extend(args.cloned());
21 break;
22 }
23 "--from" | "--shell" | "--module" => {
24 let value = args
25 .next()
26 .filter(|value| !value.starts_with("--") && !value.is_empty())
27 .ok_or_else(|| format!("Missing value for {arg}"))?;
28 match arg.as_str() {
29 "--from" => result.options.from.clone_from(value),
30 "--shell" => result.options.shell = Some(value.clone()),
31 _ => result.options.module_name.clone_from(value),
32 }
33 }
34 "--no-use-m" => result.options.use_m = false,
35 "--no-shebang" => result.options.shebang = false,
36 "--commonjs" => result.options.commonjs = true,
37 "--structural" => result.structural = true,
38 "--help" | "-h" => result.help = true,
39 "-" => result.positional.push(arg.clone()),
40 value if value.starts_with('-') => return Err(format!("Unknown option: {value}")),
41 _ => result.positional.push(arg.clone()),
42 }
43 }
44 if result.positional.len() > 2 {
45 return Err("Expected input and optional output filename".into());
46 }
47 result.options.filename = result.positional.first().cloned().unwrap_or_default();
48 Ok(result)
49}