diff --git a/config.sample.fire.yml b/config.sample.fire.yml index 381afa6..b2be978 100644 --- a/config.sample.fire.yml +++ b/config.sample.fire.yml @@ -1,8 +1,6 @@ # yaml-language-server: $schema=./schemas/fire.schema.json dir: . # default working directory "." -env_file: .env # default env file - namespace: description: Example prefix: ex @@ -73,7 +71,7 @@ commands: description: | y si agrego descripción a este? vamos - exec: npm run --key ${SECRET_KEY:-default_value} + exec: npm run run2: before: docker compose ps -q front | grep -q . || docker compose up -d front @@ -81,7 +79,6 @@ commands: run3: runner: docker compose exec front /bin/bash # equivalent to run inside the container - env_file: .env.front # override the env file for this command exec: # multiple commands run in sequence; the last one receives the args - npm run build - npm run start @@ -167,6 +164,3 @@ commands: - "CMD echo {2} is a beautiful place!" - "CMD echo and who else is with you? ...{{n}}" - "CMD echo and who else is with you? [{{n}}]" - - web: - delegate: npm-run # built-in delegate that uses package.json scripts for autocompletion diff --git a/schemas/fire.schema.json b/schemas/fire.schema.json index 3054530..a2735a2 100644 --- a/schemas/fire.schema.json +++ b/schemas/fire.schema.json @@ -9,10 +9,6 @@ "type": "string", "description": "Project working directory. Defaults to '.'." }, - "env_file": { - "type": "string", - "description": "Environment file path loaded before command execution. Defaults to '.env'." - }, "namespace": { "$ref": "#/$defs/namespace" }, @@ -168,10 +164,6 @@ "type": "string", "description": "Command-specific working directory. Overrides top-level `dir`." }, - "env_file": { - "type": "string", - "description": "Command-specific environment file. Overrides top-level `env_file`." - }, "check": { "type": "string", "description": "Pre-check command; can be paired with `fallback_runner`." @@ -192,10 +184,6 @@ "type": "string", "description": "Short user-facing description shown in listings and completion." }, - "delegate": { - "type": "string", - "description": "Built-in delegate used to generate dynamic subcommands (for example: npm-run)." - }, "placeholder": { "type": "string", "description": "Placeholder template for this command (same semantics as `x-arg-config.placeholder`)." @@ -250,11 +238,6 @@ "eval" ] }, - { - "required": [ - "delegate" - ] - }, { "required": [ "commands" diff --git a/src/execute.rs b/src/execute.rs index e22f19a..ae8216d 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -792,43 +792,41 @@ fn enforce_unused_args_policy( return; } - let placeholder_configured = context.placeholder.is_some(); - let policy_configured = context.on_unused_args.is_some(); - if !placeholder_configured && !policy_configured && !stats.had_placeholders { + let mode = context.on_unused_args.unwrap_or(UnusedArgsMode::Ignore); + if matches!(mode, UnusedArgsMode::Ignore) { return; } - let unused_args = remaining_args + let unused_indexes = remaining_args .iter() .enumerate() - .filter_map(|(index, arg)| { + .filter_map(|(index, _)| { if stats.used_arg_indexes.contains(&index) { None } else { - Some(arg.clone()) + Some(index + 1) } }) .collect::>(); - let mode = context.on_unused_args.unwrap_or(UnusedArgsMode::Error); + if unused_indexes.is_empty() { + return; + } + match mode { UnusedArgsMode::Ignore => {} UnusedArgsMode::Warn => { - if !unused_args.is_empty() { - eprintln!( - "[fire] Warning: unused args ignored: {}", - join_shell_args(&unused_args) - ); - } + eprintln!( + "[fire] Warning: unused arguments detected: {:?}", + unused_indexes + ); } UnusedArgsMode::Error => { - if !unused_args.is_empty() { - eprintln!( - "[fire] Unused args are not allowed: {}", - join_shell_args(&unused_args) - ); - process::exit(1); - } + eprintln!( + "[fire] Error: unused arguments detected: {:?}", + unused_indexes + ); + process::exit(1); } } } @@ -1076,13 +1074,12 @@ fn unresolved_args_for_tail( } let placeholder_configured = context.placeholder.is_some(); - let policy_configured = context.on_unused_args.is_some(); - if !placeholder_configured && !policy_configured && !stats.had_placeholders { + if !placeholder_configured && !stats.had_placeholders { return remaining_args.to_vec(); } - let unused_args = remaining_args + remaining_args .iter() .enumerate() .filter_map(|(index, arg)| { @@ -1092,31 +1089,7 @@ fn unresolved_args_for_tail( Some(arg.clone()) } }) - .collect::>(); - - let mode = context.on_unused_args.unwrap_or(UnusedArgsMode::Error); - match mode { - UnusedArgsMode::Ignore => Vec::new(), - UnusedArgsMode::Warn => { - if !unused_args.is_empty() { - eprintln!( - "[fire] Warning: unused args ignored: {}", - join_shell_args(&unused_args) - ); - } - Vec::new() - } - UnusedArgsMode::Error => { - if !unused_args.is_empty() { - eprintln!( - "[fire] Unused args are not allowed: {}", - join_shell_args(&unused_args) - ); - process::exit(1); - } - Vec::new() - } - } + .collect::>() } fn render_runtime_string( @@ -2012,27 +1985,72 @@ mod tests { } #[test] - fn unresolved_args_respects_ignore_policy() { + fn unresolved_args_respects_consumed_indexes_without_policy_effect() { + let mut stats = RenderStats::default(); + stats.had_placeholders = true; + stats.used_arg_indexes.insert(0); let context = ExecutionContext { - on_unused_args: Some(UnusedArgsMode::Ignore), + on_unused_args: Some(UnusedArgsMode::Error), ..ExecutionContext::default() }; + let args = vec!["one".to_string(), "two".to_string()]; + assert_eq!( + unresolved_args_for_tail(&context, &args, &stats), + vec!["two".to_string()] + ); + } + + #[test] + fn unused_args_policy_defaults_to_ignore_for_eval() { + let context = ExecutionContext::default(); let stats = RenderStats::default(); - let args = vec!["one".to_string()]; - assert!(unresolved_args_for_tail(&context, &args, &stats).is_empty()); + let args = vec!["one".to_string(), "two".to_string()]; + enforce_unused_args_policy(&context, &args, &stats); } #[test] - fn unresolved_args_uses_consumed_indexes() { - let mut stats = RenderStats::default(); - stats.had_placeholders = true; - stats.used_arg_indexes.insert(0); + fn unused_args_policy_warns_without_stopping_eval() { let context = ExecutionContext { - on_unused_args: Some(UnusedArgsMode::Ignore), + on_unused_args: Some(UnusedArgsMode::Warn), ..ExecutionContext::default() }; + let mut stats = RenderStats::default(); + stats.had_placeholders = true; + stats.used_arg_indexes.insert(0); let args = vec!["one".to_string(), "two".to_string()]; - assert!(unresolved_args_for_tail(&context, &args, &stats).is_empty()); + enforce_unused_args_policy(&context, &args, &stats); + } + + #[test] + fn unused_args_detection_counts_dynamic_spread_in_eval() { + let mut context = ExecutionContext::default(); + context.placeholder = Some("{{n}}".to_string()); + let mut stats = RenderStats::default(); + let args = vec!["first".to_string(), "second".to_string(), "third".to_string()]; + render_runtime_string( + "py:print({1}, ...{{n}})", + &context, + &args, + true, + RenderMode::Eval, + &mut stats, + ); + + let unused = args + .iter() + .enumerate() + .filter_map(|(index, value)| { + if stats.used_arg_indexes.contains(&index) { + None + } else { + Some(value.clone()) + } + }) + .collect::>(); + + assert!(unused.is_empty()); + assert!(stats.had_placeholders); + assert_eq!(stats.used_arg_indexes.len(), 3); } #[test]