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
3 changes: 3 additions & 0 deletions crates/bashkit-cli/src/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ impl ExitState {
}

pub async fn run(mut bash: bashkit::Bash, exit_state: Arc<ExitState>) -> Result<i32> {
// Diagnostics as `bash -i` prints them: `bash: x: ...`, no `line N:`.
bash.set_interactive(true);

// Set up interactive environment
set_interactive_env(&mut bash).await;

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/interpreter/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ impl Interpreter {
/// `prefix` names the reporting builtin (`((: `, `let: `), empty for an
/// expansion.
pub(super) fn arith_diag(&self, prefix: &str, msg: &str) -> String {
format!("bash: line {}: {prefix}{msg}\n", self.current_line)
self.diag(format!("{prefix}{msg}\n"))
}

pub(super) fn take_arith_error(&self) -> Option<String> {
Expand Down
78 changes: 42 additions & 36 deletions crates/bashkit/src/interpreter/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,10 @@ impl Interpreter {
if !kind.allowed_options().contains(c) {
let sign = if set { '-' } else { '+' };
return Ok(ExecResult::err(
format!(
"bash: {cmd}: {sign}{c}: invalid option\n{}",
self.diag(format!(
"{cmd}: {sign}{c}: invalid option\n{}",
kind.usage(cmd)
),
)),
2,
));
}
Expand Down Expand Up @@ -641,7 +641,7 @@ impl Interpreter {
}
}
return Ok(ExecResult::err(
"bash: local: can only be used in a function\n".to_string(),
self.diag("local: can only be used in a function\n"),
1,
));
}
Expand Down Expand Up @@ -703,7 +703,7 @@ impl Interpreter {
if !self.scoped.functions.contains_key(name.as_str()) {
status = 1;
if matches!(kind, DeclKind::Export | DeclKind::Readonly) {
err.push_str(&format!("bash: {cmd}: {name}: not a function\n"));
err.push_str(&self.diag(format!("{cmd}: {name}: not a function\n")));
}
} else if matches!(kind, DeclKind::Declare | DeclKind::Local) {
out.push_str(&show(name, false));
Expand Down Expand Up @@ -733,7 +733,7 @@ impl Interpreter {
match self.format_declare_line(name) {
Some(line) => out.push_str(&line),
None => {
err.push_str(&format!("bash: {cmd}: {name}: not found\n"));
err.push_str(&self.diag(format!("{cmd}: {name}: not found\n")));
status = 1;
}
}
Expand Down Expand Up @@ -832,10 +832,10 @@ impl Interpreter {
_ => (lhs, None),
};
if !is_valid_var_name(base) {
return Ok(Some(format!(
"bash: {cmd}: `{}': not a valid identifier\n",
return Ok(Some(self.diag(format!(
"{cmd}: `{}': not a valid identifier\n",
display_arg()
)));
))));
}
// THREAT[TM-INJ-012/014/015]: internal marker names are never declared.
if is_internal_variable(base) {
Expand Down Expand Up @@ -870,9 +870,9 @@ impl Interpreter {
match self.resolve_nameref_strict(lhs) {
Ok(t) => t,
Err(()) => {
return Ok(Some(format!(
"bash: {cmd}: warning: {base}: circular name reference\n"
)));
return Ok(Some(self.diag(format!(
"{cmd}: warning: {base}: circular name reference\n"
))));
}
}
};
Expand Down Expand Up @@ -901,7 +901,9 @@ impl Interpreter {
.cloned()
.unwrap_or_default();
if saved.attrs.is_some_and(|a| a.contains(VarAttrs::READONLY)) {
return Ok(Some(format!("bash: {cmd}: {name}: readonly variable\n")));
return Ok(Some(
self.diag(format!("{cmd}: {name}: readonly variable\n")),
));
}
let new_value = if append {
saved.value.clone().unwrap_or_default() + v
Expand Down Expand Up @@ -939,7 +941,9 @@ impl Interpreter {

if make_local && !self.is_local_in_current_frame(name) {
if self.is_var_readonly(name) {
return Ok(Some(format!("bash: {cmd}: {name}: readonly variable\n")));
return Ok(Some(
self.diag(format!("{cmd}: {name}: readonly variable\n")),
));
}
self.make_local(name);
}
Expand All @@ -949,7 +953,9 @@ impl Interpreter {
if self.is_var_readonly(name)
&& (value.is_some() || opts.off.readonly || opts.off.export && kind == DeclKind::Export)
{
return Ok(Some(format!("bash: {cmd}: {name}: readonly variable\n")));
return Ok(Some(
self.diag(format!("{cmd}: {name}: readonly variable\n")),
));
}

let is_compound = matches!(value, Some(DeclValue::Compound(_)));
Expand All @@ -971,7 +977,7 @@ impl Interpreter {
None
};
if let Some(what) = conversion {
let msg = format!("bash: {cmd}: {name}: cannot convert {what} array\n");
let msg = self.diag(format!("{cmd}: {name}: cannot convert {what} array\n"));
if is_compound {
// With an array value bash abandons the whole command line.
return Err(crate::error::Error::LineAbort(msg));
Expand Down Expand Up @@ -1015,9 +1021,9 @@ impl Interpreter {
None => {}
Some(DeclValue::Compound(words)) => {
if subscript.is_some() {
return Ok(Some(format!(
"bash: {cmd}: {name}: cannot assign list to array member\n"
)));
return Ok(Some(self.diag(format!(
"{cmd}: {name}: cannot assign list to array member\n"
))));
}
let assoc = self.var_kind(name) == VarKind::Assoc;
self.assign_array_words(name, &words, append, assoc).await?;
Expand Down Expand Up @@ -1106,32 +1112,32 @@ impl Interpreter {
let target = match value {
Some(DeclValue::Str(t)) => Some(t),
Some(DeclValue::Compound(_)) => {
return Some(format!(
"bash: {cmd}: {name}: reference variable cannot be an array\n"
));
return Some(self.diag(format!(
"{cmd}: {name}: reference variable cannot be an array\n"
)));
}
None => None,
};
if let Some(t) = &target {
if !valid_nameref_target(t) {
return Some(format!(
"bash: {cmd}: `{t}': invalid variable name for name reference\n"
));
return Some(self.diag(format!(
"{cmd}: `{t}': invalid variable name for name reference\n"
)));
}
if t == name && !make_local {
return Some(format!(
"bash: {cmd}: {name}: nameref variable self references not allowed\n"
));
return Some(self.diag(format!(
"{cmd}: {name}: nameref variable self references not allowed\n"
)));
}
}
if make_local && !self.is_local_in_current_frame(name) {
if self.is_var_readonly(name) {
return Some(format!("bash: {cmd}: {name}: readonly variable\n"));
return Some(self.diag(format!("{cmd}: {name}: readonly variable\n")));
}
self.make_local(name);
}
if target.is_some() && self.is_var_readonly(name) {
return Some(format!("bash: {cmd}: {name}: readonly variable\n"));
return Some(self.diag(format!("{cmd}: {name}: readonly variable\n")));
}
let target = match target {
Some(t) => t,
Expand All @@ -1144,9 +1150,9 @@ impl Interpreter {
v
}
Some(v) => {
return Some(format!(
"bash: {cmd}: `{v}': invalid variable name for name reference\n"
));
return Some(self.diag(format!(
"{cmd}: `{v}': invalid variable name for name reference\n"
)));
}
None => String::new(),
},
Expand Down Expand Up @@ -1389,9 +1395,9 @@ impl Interpreter {
let len = map.keys().max().map_or(0, |m| m + 1) as i64;
let i = len + raw;
if i < 0 {
return Err(crate::error::Error::LineAbort(format!(
"bash: {name}[{key_text}]: bad array subscript\n"
)));
return Err(crate::error::Error::LineAbort(
self.diag(format!("{name}[{key_text}]: bad array subscript\n")),
));
}
i as usize
} else {
Expand Down
22 changes: 11 additions & 11 deletions crates/bashkit/src/interpreter/expansion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ impl Interpreter {
for part in &word.parts {
match part {
WordPart::BadSubstitution(text) => {
return Err(crate::error::Error::LineAbort(format!(
"bash: {text}: bad substitution\n"
)));
return Err(crate::error::Error::LineAbort(
self.diag(format!("{text}: bad substitution\n")),
));
}
WordPart::CompoundAssignment { .. } => {
// Only declaration builtins take `name=(...)`; elsewhere
Expand Down Expand Up @@ -364,7 +364,7 @@ impl Interpreter {
}
WordPart::Variable(name) => {
if self.is_nounset() && !self.is_variable_set(name) {
self.nounset_error = Some(format!("bash: {}: unbound variable\n", name));
self.nounset_error = Some(self.unbound_variable_diag(name));
}
if name == "*" && word.quoted {
let positional = self
Expand Down Expand Up @@ -446,7 +446,7 @@ impl Interpreter {
| ParameterOp::Error
)
{
self.nounset_error = Some("bash: ${}: bad substitution\n".to_string());
self.nounset_error = Some(self.diag("${}: bad substitution\n"));
continue;
}

Expand All @@ -465,13 +465,13 @@ impl Interpreter {
&& !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
&& (!is_set || (*colon_variant && value.is_empty()))
{
return Err(crate::error::Error::LineAbort(format!(
"bash: ${name}: cannot assign in this way\n"
)));
return Err(crate::error::Error::LineAbort(
self.diag(format!("${name}: cannot assign in this way\n")),
));
}

if self.is_nounset() && !suppress_nounset && !is_set {
self.nounset_error = Some(format!("bash: {}: unbound variable\n", name));
self.nounset_error = Some(self.diag(format!("{name}: unbound variable\n")));
}

if operand.contains("$(") {
Expand Down Expand Up @@ -1659,9 +1659,9 @@ impl Interpreter {
if use_default {
let expanded = self.expand_operand(operand);
let msg = if expanded.is_empty() {
format!("bash: {}: parameter null or not set\n", name)
self.diag(format!("{}: parameter null or not set\n", name))
} else {
format!("bash: {}: {}\n", name, expanded)
self.diag(format!("{}: {}\n", name, expanded))
};
self.nounset_error = Some(msg);
String::new()
Expand Down
Loading
Loading