command arguments are now underlined if they match an existing path -m ran rustfmt on the entire codebase

This commit is contained in:
2026-02-19 21:32:03 -05:00
parent 959ea9346a
commit 74988166f0
44 changed files with 5549 additions and 5019 deletions

View File

@@ -1,14 +1,34 @@
use std::sync::LazyLock;
use crate::{
builtin::setup_builtin, expand::expand_prompt, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state
builtin::setup_builtin,
expand::expand_prompt,
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{IoStack, borrow_fd},
state,
};
pub const ECHO_OPTS: [OptSpec;4] = [
OptSpec { opt: Opt::Short('n'), takes_arg: false },
OptSpec { opt: Opt::Short('E'), takes_arg: false },
OptSpec { opt: Opt::Short('e'), takes_arg: false },
OptSpec { opt: Opt::Short('p'), takes_arg: false },
pub const ECHO_OPTS: [OptSpec; 4] = [
OptSpec {
opt: Opt::Short('n'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('E'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('e'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('p'),
takes_arg: false,
},
];
bitflags! {
@@ -16,7 +36,7 @@ bitflags! {
const NO_NEWLINE = 0b000001;
const USE_STDERR = 0b000010;
const USE_ESCAPE = 0b000100;
const USE_PROMPT = 0b001000;
const USE_PROMPT = 0b001000;
}
}
@@ -40,13 +60,15 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
borrow_fd(STDOUT_FILENO)
};
let mut echo_output = prepare_echo_args(argv
.into_iter()
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
.collect::<Vec<_>>(),
flags.contains(EchoFlags::USE_ESCAPE),
flags.contains(EchoFlags::USE_PROMPT)
)?.join(" ");
let mut echo_output = prepare_echo_args(
argv
.into_iter()
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
.collect::<Vec<_>>(),
flags.contains(EchoFlags::USE_ESCAPE),
flags.contains(EchoFlags::USE_PROMPT),
)?
.join(" ");
if !flags.contains(EchoFlags::NO_NEWLINE) && !echo_output.ends_with('\n') {
echo_output.push('\n')
@@ -58,137 +80,141 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
Ok(())
}
pub fn prepare_echo_args(argv: Vec<String>, use_escape: bool, use_prompt: bool) -> ShResult<Vec<String>> {
if !use_escape {
if use_prompt {
let expanded: ShResult<Vec<String>> = argv
.into_iter()
.map(|s| expand_prompt(s.as_str()))
.collect();
return expanded
}
return Ok(argv);
}
pub fn prepare_echo_args(
argv: Vec<String>,
use_escape: bool,
use_prompt: bool,
) -> ShResult<Vec<String>> {
if !use_escape {
if use_prompt {
let expanded: ShResult<Vec<String>> = argv
.into_iter()
.map(|s| expand_prompt(s.as_str()))
.collect();
return expanded;
}
return Ok(argv);
}
let mut prepared_args = Vec::with_capacity(argv.len());
let mut prepared_args = Vec::with_capacity(argv.len());
for arg in argv {
let mut prepared_arg = String::new();
if use_prompt {
prepared_arg = expand_prompt(&prepared_arg)?;
}
for arg in argv {
let mut prepared_arg = String::new();
if use_prompt {
prepared_arg = expand_prompt(&prepared_arg)?;
}
let mut chars = arg.chars().peekable();
let mut chars = arg.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&next_char) = chars.peek() {
match next_char {
'n' => {
prepared_arg.push('\n');
chars.next();
}
't' => {
prepared_arg.push('\t');
chars.next();
}
'r' => {
prepared_arg.push('\r');
chars.next();
}
'a' => {
prepared_arg.push('\x07');
chars.next();
}
'b' => {
prepared_arg.push('\x08');
chars.next();
}
'e' | 'E' => {
prepared_arg.push('\x1b');
chars.next();
}
'x' => {
chars.next(); // consume 'x'
let mut hex_digits = String::new();
for _ in 0..2 {
if let Some(&hex_char) = chars.peek() {
if hex_char.is_ascii_hexdigit() {
hex_digits.push(hex_char);
chars.next();
} else {
break;
}
} else {
break;
}
}
if let Ok(value) = u8::from_str_radix(&hex_digits, 16) {
prepared_arg.push(value as char);
} else {
prepared_arg.push('\\');
prepared_arg.push('x');
prepared_arg.push_str(&hex_digits);
}
}
'0' => {
chars.next(); // consume '0'
let mut octal_digits = String::new();
for _ in 0..3 {
if let Some(&octal_char) = chars.peek() {
if ('0'..='7').contains(&octal_char) {
octal_digits.push(octal_char);
chars.next();
} else {
break;
}
} else {
break;
}
}
if let Ok(value) = u8::from_str_radix(&octal_digits, 8) {
prepared_arg.push(value as char);
} else {
prepared_arg.push('\\');
prepared_arg.push('0');
prepared_arg.push_str(&octal_digits);
}
}
'\\' => {
prepared_arg.push('\\');
chars.next();
}
_ => prepared_arg.push(c),
}
} else {
prepared_arg.push(c);
}
} else {
prepared_arg.push(c);
}
}
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&next_char) = chars.peek() {
match next_char {
'n' => {
prepared_arg.push('\n');
chars.next();
}
't' => {
prepared_arg.push('\t');
chars.next();
}
'r' => {
prepared_arg.push('\r');
chars.next();
}
'a' => {
prepared_arg.push('\x07');
chars.next();
}
'b' => {
prepared_arg.push('\x08');
chars.next();
}
'e' | 'E' => {
prepared_arg.push('\x1b');
chars.next();
}
'x' => {
chars.next(); // consume 'x'
let mut hex_digits = String::new();
for _ in 0..2 {
if let Some(&hex_char) = chars.peek() {
if hex_char.is_ascii_hexdigit() {
hex_digits.push(hex_char);
chars.next();
} else {
break;
}
} else {
break;
}
}
if let Ok(value) = u8::from_str_radix(&hex_digits, 16) {
prepared_arg.push(value as char);
} else {
prepared_arg.push('\\');
prepared_arg.push('x');
prepared_arg.push_str(&hex_digits);
}
}
'0' => {
chars.next(); // consume '0'
let mut octal_digits = String::new();
for _ in 0..3 {
if let Some(&octal_char) = chars.peek() {
if ('0'..='7').contains(&octal_char) {
octal_digits.push(octal_char);
chars.next();
} else {
break;
}
} else {
break;
}
}
if let Ok(value) = u8::from_str_radix(&octal_digits, 8) {
prepared_arg.push(value as char);
} else {
prepared_arg.push('\\');
prepared_arg.push('0');
prepared_arg.push_str(&octal_digits);
}
}
'\\' => {
prepared_arg.push('\\');
chars.next();
}
_ => prepared_arg.push(c),
}
} else {
prepared_arg.push(c);
}
} else {
prepared_arg.push(c);
}
}
prepared_args.push(prepared_arg);
}
prepared_args.push(prepared_arg);
}
Ok(prepared_args)
Ok(prepared_args)
}
pub fn get_echo_flags(opts: Vec<Opt>) -> ShResult<EchoFlags> {
let mut flags = EchoFlags::empty();
for opt in opts {
for opt in opts {
match opt {
Opt::Short('n') => flags |= EchoFlags::NO_NEWLINE,
Opt::Short('r') => flags |= EchoFlags::USE_STDERR,
Opt::Short('e') => flags |= EchoFlags::USE_ESCAPE,
Opt::Short('p') => flags |= EchoFlags::USE_PROMPT,
_ => {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("echo: Unexpected flag '{opt}'"),
));
}
Opt::Short('p') => flags |= EchoFlags::USE_PROMPT,
_ => {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("echo: Unexpected flag '{opt}'"),
));
}
}
}