Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions config.sample.fire.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -73,15 +71,14 @@ 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
exec: compose exec front npm run

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
Expand Down Expand Up @@ -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
17 changes: 0 additions & 17 deletions schemas/fire.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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`."
Expand All @@ -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`)."
Expand Down Expand Up @@ -250,11 +238,6 @@
"eval"
]
},
{
"required": [
"delegate"
]
},
{
"required": [
"commands"
Expand Down
134 changes: 76 additions & 58 deletions src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();

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);
}
}
}
Expand Down Expand Up @@ -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)| {
Expand All @@ -1092,31 +1089,7 @@ fn unresolved_args_for_tail(
Some(arg.clone())
}
})
.collect::<Vec<_>>();

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::<Vec<_>>()
}

fn render_runtime_string(
Expand Down Expand Up @@ -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::<Vec<_>>();

assert!(unused.is_empty());
assert!(stats.had_placeholders);
assert_eq!(stats.used_arg_indexes.len(), 3);
}

#[test]
Expand Down