command arguments are now underlined if they match an existing path -m ran rustfmt on the entire codebase
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::expand::{perform_param_expansion, DUB_QUOTE, VAR_SUB};
|
||||
use crate::expand::{DUB_QUOTE, VAR_SUB, perform_param_expansion};
|
||||
use crate::state::VarFlags;
|
||||
|
||||
use super::*;
|
||||
@@ -293,70 +293,78 @@ fn param_expansion_replacesuffix() {
|
||||
|
||||
#[test]
|
||||
fn dquote_escape_dollar() {
|
||||
// "\$foo" should strip backslash, produce literal $foo (no expansion)
|
||||
let result = unescape_str(r#""\$foo""#);
|
||||
assert!(!result.contains(VAR_SUB), "Escaped $ should not become VAR_SUB");
|
||||
assert!(result.contains('$'), "Literal $ should be preserved");
|
||||
assert!(!result.contains('\\'), "Backslash should be stripped");
|
||||
// "\$foo" should strip backslash, produce literal $foo (no expansion)
|
||||
let result = unescape_str(r#""\$foo""#);
|
||||
assert!(
|
||||
!result.contains(VAR_SUB),
|
||||
"Escaped $ should not become VAR_SUB"
|
||||
);
|
||||
assert!(result.contains('$'), "Literal $ should be preserved");
|
||||
assert!(!result.contains('\\'), "Backslash should be stripped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_escape_backslash() {
|
||||
// "\\" in double quotes should produce a single backslash
|
||||
let result = unescape_str(r#""\\""#);
|
||||
let inner: String = result.chars()
|
||||
.filter(|&c| c != DUB_QUOTE)
|
||||
.collect();
|
||||
assert_eq!(inner, "\\", "Double backslash should produce single backslash");
|
||||
// "\\" in double quotes should produce a single backslash
|
||||
let result = unescape_str(r#""\\""#);
|
||||
let inner: String = result.chars().filter(|&c| c != DUB_QUOTE).collect();
|
||||
assert_eq!(
|
||||
inner, "\\",
|
||||
"Double backslash should produce single backslash"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_escape_quote() {
|
||||
// "\"" should produce a literal double quote
|
||||
let result = unescape_str(r#""\"""#);
|
||||
let inner: String = result.chars()
|
||||
.filter(|&c| c != DUB_QUOTE)
|
||||
.collect();
|
||||
assert!(inner.contains('"'), "Escaped quote should produce literal quote");
|
||||
// "\"" should produce a literal double quote
|
||||
let result = unescape_str(r#""\"""#);
|
||||
let inner: String = result.chars().filter(|&c| c != DUB_QUOTE).collect();
|
||||
assert!(
|
||||
inner.contains('"'),
|
||||
"Escaped quote should produce literal quote"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_escape_backtick() {
|
||||
// "\`" should strip backslash, produce literal backtick
|
||||
let result = unescape_str(r#""\`""#);
|
||||
let inner: String = result.chars()
|
||||
.filter(|&c| c != DUB_QUOTE)
|
||||
.collect();
|
||||
assert_eq!(inner, "`", "Escaped backtick should produce literal backtick");
|
||||
// "\`" should strip backslash, produce literal backtick
|
||||
let result = unescape_str(r#""\`""#);
|
||||
let inner: String = result.chars().filter(|&c| c != DUB_QUOTE).collect();
|
||||
assert_eq!(
|
||||
inner, "`",
|
||||
"Escaped backtick should produce literal backtick"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_escape_nonspecial_preserves_backslash() {
|
||||
// "\a" inside double quotes should preserve the backslash (a is not special)
|
||||
let result = unescape_str(r#""\a""#);
|
||||
let inner: String = result.chars()
|
||||
.filter(|&c| c != DUB_QUOTE)
|
||||
.collect();
|
||||
assert_eq!(inner, "\\a", "Backslash before non-special char should be preserved");
|
||||
// "\a" inside double quotes should preserve the backslash (a is not special)
|
||||
let result = unescape_str(r#""\a""#);
|
||||
let inner: String = result.chars().filter(|&c| c != DUB_QUOTE).collect();
|
||||
assert_eq!(
|
||||
inner, "\\a",
|
||||
"Backslash before non-special char should be preserved"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_unescaped_dollar_expands() {
|
||||
// "$foo" inside double quotes should produce VAR_SUB (expansion marker)
|
||||
let result = unescape_str(r#""$foo""#);
|
||||
assert!(result.contains(VAR_SUB), "Unescaped $ should become VAR_SUB");
|
||||
// "$foo" inside double quotes should produce VAR_SUB (expansion marker)
|
||||
let result = unescape_str(r#""$foo""#);
|
||||
assert!(
|
||||
result.contains(VAR_SUB),
|
||||
"Unescaped $ should become VAR_SUB"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dquote_mixed_escapes() {
|
||||
// "hello \$world \\end" should have literal $, single backslash
|
||||
let result = unescape_str(r#""hello \$world \\end""#);
|
||||
assert!(!result.contains(VAR_SUB), "Escaped $ should not expand");
|
||||
assert!(result.contains('$'), "Literal $ should be in output");
|
||||
// Should have exactly one backslash (from \\)
|
||||
let inner: String = result.chars()
|
||||
.filter(|&c| c != DUB_QUOTE)
|
||||
.collect();
|
||||
let backslash_count = inner.chars().filter(|&c| c == '\\').count();
|
||||
assert_eq!(backslash_count, 1, "\\\\ should produce one backslash");
|
||||
// "hello \$world \\end" should have literal $, single backslash
|
||||
let result = unescape_str(r#""hello \$world \\end""#);
|
||||
assert!(!result.contains(VAR_SUB), "Escaped $ should not expand");
|
||||
assert!(result.contains('$'), "Literal $ should be in output");
|
||||
// Should have exactly one backslash (from \\)
|
||||
let inner: String = result.chars().filter(|&c| c != DUB_QUOTE).collect();
|
||||
let backslash_count = inner.chars().filter(|&c| c == '\\').count();
|
||||
assert_eq!(backslash_count, 1, "\\\\ should produce one backslash");
|
||||
}
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
use crate::prompt::readline::{
|
||||
annotate_input, annotate_input_recursive, markers,
|
||||
highlight::Highlighter,
|
||||
annotate_input, annotate_input_recursive, highlight::Highlighter, markers,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Helper to check if a marker exists at any position in the annotated string
|
||||
fn has_marker(annotated: &str, marker: char) -> bool {
|
||||
annotated.contains(marker)
|
||||
annotated.contains(marker)
|
||||
}
|
||||
|
||||
/// Helper to find the position of a marker in the annotated string
|
||||
fn find_marker(annotated: &str, marker: char) -> Option<usize> {
|
||||
annotated.find(marker)
|
||||
annotated.find(marker)
|
||||
}
|
||||
|
||||
/// Helper to check if markers appear in the correct order
|
||||
fn marker_before(annotated: &str, first: char, second: char) -> bool {
|
||||
if let (Some(pos1), Some(pos2)) = (find_marker(annotated, first), find_marker(annotated, second)) {
|
||||
pos1 < pos2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let (Some(pos1), Some(pos2)) = (
|
||||
find_marker(annotated, first),
|
||||
find_marker(annotated, second),
|
||||
) {
|
||||
pos1 < pos2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -30,69 +32,70 @@ fn marker_before(annotated: &str, first: char, second: char) -> bool {
|
||||
|
||||
#[test]
|
||||
fn annotate_simple_command() {
|
||||
let input = "/bin/ls -la";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "/bin/ls -la";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have COMMAND marker for "/bin/ls" (external command)
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// Should have COMMAND marker for "/bin/ls" (external command)
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
|
||||
// Should have ARG marker for "-la"
|
||||
assert!(has_marker(&annotated, markers::ARG));
|
||||
// Should have ARG marker for "-la"
|
||||
assert!(has_marker(&annotated, markers::ARG));
|
||||
|
||||
// Should have RESET markers
|
||||
assert!(has_marker(&annotated, markers::RESET));
|
||||
// Should have RESET markers
|
||||
assert!(has_marker(&annotated, markers::RESET));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_builtin_command() {
|
||||
let input = "export FOO=bar";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "export FOO=bar";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should mark "export" as BUILTIN
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
// Should mark "export" as BUILTIN
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
|
||||
// Should mark assignment (or ARG if assignment isn't specifically marked separately)
|
||||
assert!(has_marker(&annotated, markers::ASSIGNMENT) || has_marker(&annotated, markers::ARG));
|
||||
// Should mark assignment (or ARG if assignment isn't specifically marked
|
||||
// separately)
|
||||
assert!(has_marker(&annotated, markers::ASSIGNMENT) || has_marker(&annotated, markers::ARG));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_operator() {
|
||||
let input = "ls | grep foo";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "ls | grep foo";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have OPERATOR marker for pipe
|
||||
assert!(has_marker(&annotated, markers::OPERATOR));
|
||||
// Should have OPERATOR marker for pipe
|
||||
assert!(has_marker(&annotated, markers::OPERATOR));
|
||||
|
||||
// Should have COMMAND markers for both commands
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert_eq!(command_count, 2);
|
||||
// Should have COMMAND markers for both commands
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert_eq!(command_count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_redirect() {
|
||||
let input = "echo hello > output.txt";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo hello > output.txt";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have REDIRECT marker
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
// Should have REDIRECT marker
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_keyword() {
|
||||
let input = "if true; then echo yes; fi";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "if true; then echo yes; fi";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have KEYWORD markers for if/then/fi
|
||||
assert!(has_marker(&annotated, markers::KEYWORD));
|
||||
// Should have KEYWORD markers for if/then/fi
|
||||
assert!(has_marker(&annotated, markers::KEYWORD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_command_separator() {
|
||||
let input = "echo foo; echo bar";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo foo; echo bar";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have CMD_SEP marker for semicolon
|
||||
assert!(has_marker(&annotated, markers::CMD_SEP));
|
||||
// Should have CMD_SEP marker for semicolon
|
||||
assert!(has_marker(&annotated, markers::CMD_SEP));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -101,83 +104,87 @@ fn annotate_command_separator() {
|
||||
|
||||
#[test]
|
||||
fn annotate_variable_simple() {
|
||||
let input = "echo $foo";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo $foo";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have VAR_SUB markers
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB_END));
|
||||
// Should have VAR_SUB markers
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_variable_braces() {
|
||||
let input = "echo ${foo}";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo ${foo}";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have VAR_SUB markers for ${foo}
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB_END));
|
||||
// Should have VAR_SUB markers for ${foo}
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_double_quoted_string() {
|
||||
let input = r#"echo "hello world""#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#"echo "hello world""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have STRING_DQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ_END));
|
||||
// Should have STRING_DQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_single_quoted_string() {
|
||||
let input = "echo 'hello world'";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo 'hello world'";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ_END));
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_variable_in_string() {
|
||||
let input = r#"echo "hello $USER""#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#"echo "hello $USER""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have both STRING_DQ and VAR_SUB markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
// Should have both STRING_DQ and VAR_SUB markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::VAR_SUB));
|
||||
|
||||
// VAR_SUB should be inside STRING_DQ
|
||||
assert!(marker_before(&annotated, markers::STRING_DQ, markers::VAR_SUB));
|
||||
// VAR_SUB should be inside STRING_DQ
|
||||
assert!(marker_before(
|
||||
&annotated,
|
||||
markers::STRING_DQ,
|
||||
markers::VAR_SUB
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_glob_asterisk() {
|
||||
let input = "ls *.txt";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "ls *.txt";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have GLOB marker for *
|
||||
assert!(has_marker(&annotated, markers::GLOB));
|
||||
// Should have GLOB marker for *
|
||||
assert!(has_marker(&annotated, markers::GLOB));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_glob_question() {
|
||||
let input = "ls file?.txt";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "ls file?.txt";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have GLOB marker for ?
|
||||
assert!(has_marker(&annotated, markers::GLOB));
|
||||
// Should have GLOB marker for ?
|
||||
assert!(has_marker(&annotated, markers::GLOB));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_glob_bracket() {
|
||||
let input = "ls file[abc].txt";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "ls file[abc].txt";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have GLOB markers for bracket expression
|
||||
let glob_count = annotated.chars().filter(|&c| c == markers::GLOB).count();
|
||||
assert!(glob_count >= 2); // Opening and closing
|
||||
// Should have GLOB markers for bracket expression
|
||||
let glob_count = annotated.chars().filter(|&c| c == markers::GLOB).count();
|
||||
assert!(glob_count >= 2); // Opening and closing
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -186,32 +193,32 @@ fn annotate_glob_bracket() {
|
||||
|
||||
#[test]
|
||||
fn annotate_command_sub_basic() {
|
||||
let input = "echo $(whoami)";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo $(whoami)";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have CMD_SUB markers (but not recursively annotated yet)
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
// Should have CMD_SUB markers (but not recursively annotated yet)
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_subshell_basic() {
|
||||
let input = "(cd /tmp && ls)";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "(cd /tmp && ls)";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have SUBSH markers
|
||||
assert!(has_marker(&annotated, markers::SUBSH));
|
||||
assert!(has_marker(&annotated, markers::SUBSH_END));
|
||||
// Should have SUBSH markers
|
||||
assert!(has_marker(&annotated, markers::SUBSH));
|
||||
assert!(has_marker(&annotated, markers::SUBSH_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_process_sub_output() {
|
||||
let input = "diff <(ls dir1) <(ls dir2)";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "diff <(ls dir1) <(ls dir2)";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have PROC_SUB markers
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB));
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB_END));
|
||||
// Should have PROC_SUB markers
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB));
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB_END));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -220,88 +227,97 @@ fn annotate_process_sub_output() {
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_command_sub() {
|
||||
let input = "echo $(whoami)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "echo $(whoami)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have CMD_SUB markers
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
// Should have CMD_SUB markers
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
|
||||
// Inside the command sub, "whoami" should be marked as COMMAND
|
||||
// The recursive annotator should have processed the inside
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// Inside the command sub, "whoami" should be marked as COMMAND
|
||||
// The recursive annotator should have processed the inside
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_nested_command_sub() {
|
||||
let input = "echo $(echo $(whoami))";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "echo $(echo $(whoami))";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have multiple CMD_SUB markers (nested)
|
||||
let cmd_sub_count = annotated.chars().filter(|&c| c == markers::CMD_SUB).count();
|
||||
assert!(cmd_sub_count >= 2, "Should have at least 2 CMD_SUB markers for nested substitutions");
|
||||
// Should have multiple CMD_SUB markers (nested)
|
||||
let cmd_sub_count = annotated.chars().filter(|&c| c == markers::CMD_SUB).count();
|
||||
assert!(
|
||||
cmd_sub_count >= 2,
|
||||
"Should have at least 2 CMD_SUB markers for nested substitutions"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_command_sub_with_args() {
|
||||
let input = "echo $(grep foo file.txt)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "echo $(grep foo file.txt)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have BUILTIN for echo and possibly COMMAND for grep (if in PATH)
|
||||
// Just check that we have command-type markers
|
||||
let builtin_count = annotated.chars().filter(|&c| c == markers::BUILTIN).count();
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert!(builtin_count + command_count >= 2, "Expected at least 2 command markers (BUILTIN or COMMAND)");
|
||||
// Should have BUILTIN for echo and possibly COMMAND for grep (if in PATH)
|
||||
// Just check that we have command-type markers
|
||||
let builtin_count = annotated.chars().filter(|&c| c == markers::BUILTIN).count();
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert!(
|
||||
builtin_count + command_count >= 2,
|
||||
"Expected at least 2 command markers (BUILTIN or COMMAND)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_subshell() {
|
||||
let input = "(echo hello; echo world)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "(echo hello; echo world)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have SUBSH markers
|
||||
assert!(has_marker(&annotated, markers::SUBSH));
|
||||
assert!(has_marker(&annotated, markers::SUBSH_END));
|
||||
// Should have SUBSH markers
|
||||
assert!(has_marker(&annotated, markers::SUBSH));
|
||||
assert!(has_marker(&annotated, markers::SUBSH_END));
|
||||
|
||||
// Inside should be annotated with BUILTIN (echo is a builtin) and CMD_SEP
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
assert!(has_marker(&annotated, markers::CMD_SEP));
|
||||
// Inside should be annotated with BUILTIN (echo is a builtin) and CMD_SEP
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
assert!(has_marker(&annotated, markers::CMD_SEP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_process_sub() {
|
||||
let input = "diff <(ls -la)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "diff <(ls -la)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have PROC_SUB markers
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB));
|
||||
// Should have PROC_SUB markers
|
||||
assert!(has_marker(&annotated, markers::PROC_SUB));
|
||||
|
||||
// ls should be marked as COMMAND inside the process sub
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// ls should be marked as COMMAND inside the process sub
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_command_sub_in_string() {
|
||||
let input = r#"echo "current user: $(whoami)""#;
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = r#"echo "current user: $(whoami)""#;
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have STRING_DQ, CMD_SUB, and COMMAND markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// Should have STRING_DQ, CMD_SUB, and COMMAND markers
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_recursive_deeply_nested() {
|
||||
let input = r#"echo "outer: $(echo "inner: $(whoami)")""#;
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = r#"echo "outer: $(echo "inner: $(whoami)")""#;
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have multiple STRING_DQ and CMD_SUB markers
|
||||
let string_count = annotated.chars().filter(|&c| c == markers::STRING_DQ).count();
|
||||
let cmd_sub_count = annotated.chars().filter(|&c| c == markers::CMD_SUB).count();
|
||||
// Should have multiple STRING_DQ and CMD_SUB markers
|
||||
let string_count = annotated
|
||||
.chars()
|
||||
.filter(|&c| c == markers::STRING_DQ)
|
||||
.count();
|
||||
let cmd_sub_count = annotated.chars().filter(|&c| c == markers::CMD_SUB).count();
|
||||
|
||||
assert!(string_count >= 2, "Should have multiple STRING_DQ markers");
|
||||
assert!(cmd_sub_count >= 2, "Should have multiple CMD_SUB markers");
|
||||
assert!(string_count >= 2, "Should have multiple STRING_DQ markers");
|
||||
assert!(cmd_sub_count >= 2, "Should have multiple CMD_SUB markers");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -310,33 +326,37 @@ fn annotate_recursive_deeply_nested() {
|
||||
|
||||
#[test]
|
||||
fn marker_priority_var_in_string() {
|
||||
let input = r#""$foo""#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#""$foo""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// STRING_DQ should come before VAR_SUB (outer before inner)
|
||||
assert!(marker_before(&annotated, markers::STRING_DQ, markers::VAR_SUB));
|
||||
// STRING_DQ should come before VAR_SUB (outer before inner)
|
||||
assert!(marker_before(
|
||||
&annotated,
|
||||
markers::STRING_DQ,
|
||||
markers::VAR_SUB
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marker_priority_arg_vs_string() {
|
||||
let input = r#"echo "hello""#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#"echo "hello""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Both ARG and STRING_DQ should be present
|
||||
// STRING_DQ should be inside the ARG token's span
|
||||
assert!(has_marker(&annotated, markers::ARG));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
// Both ARG and STRING_DQ should be present
|
||||
// STRING_DQ should be inside the ARG token's span
|
||||
assert!(has_marker(&annotated, markers::ARG));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marker_priority_reset_placement() {
|
||||
let input = "echo hello";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo hello";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// RESET markers should appear after each token
|
||||
// There should be multiple RESET markers
|
||||
let reset_count = annotated.chars().filter(|&c| c == markers::RESET).count();
|
||||
assert!(reset_count >= 2);
|
||||
// RESET markers should appear after each token
|
||||
// There should be multiple RESET markers
|
||||
let reset_count = annotated.chars().filter(|&c| c == markers::RESET).count();
|
||||
assert!(reset_count >= 2);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -345,127 +365,131 @@ fn marker_priority_reset_placement() {
|
||||
|
||||
#[test]
|
||||
fn highlighter_produces_ansi_codes() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("echo hello");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("echo hello");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
|
||||
// Should contain ANSI escape codes
|
||||
assert!(output.contains("\x1b["), "Output should contain ANSI escape sequences");
|
||||
// Should contain ANSI escape codes
|
||||
assert!(
|
||||
output.contains("\x1b["),
|
||||
"Output should contain ANSI escape sequences"
|
||||
);
|
||||
|
||||
// Should still contain the original text
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
// Should still contain the original text
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_handles_empty_input() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
|
||||
// Should not crash and should return empty or minimal output
|
||||
assert!(output.len() < 10); // Just escape codes or empty
|
||||
// Should not crash and should return empty or minimal output
|
||||
assert!(output.len() < 10); // Just escape codes or empty
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_command_validation() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
let mut highlighter = Highlighter::new();
|
||||
|
||||
// Valid command (echo exists)
|
||||
highlighter.load_input("echo test");
|
||||
highlighter.highlight();
|
||||
let valid_output = highlighter.take();
|
||||
// Valid command (echo exists)
|
||||
highlighter.load_input("echo test");
|
||||
highlighter.highlight();
|
||||
let valid_output = highlighter.take();
|
||||
|
||||
// Invalid command (definitely doesn't exist)
|
||||
highlighter.load_input("xyznotacommand123 test");
|
||||
highlighter.highlight();
|
||||
let invalid_output = highlighter.take();
|
||||
// Invalid command (definitely doesn't exist)
|
||||
highlighter.load_input("xyznotacommand123 test");
|
||||
highlighter.highlight();
|
||||
let invalid_output = highlighter.take();
|
||||
|
||||
// Both should have ANSI codes
|
||||
assert!(valid_output.contains("\x1b["));
|
||||
assert!(invalid_output.contains("\x1b["));
|
||||
// Both should have ANSI codes
|
||||
assert!(valid_output.contains("\x1b["));
|
||||
assert!(invalid_output.contains("\x1b["));
|
||||
|
||||
// The color codes should be different (green vs red)
|
||||
// Valid commands should have \x1b[32m (green)
|
||||
// Invalid commands should have \x1b[31m (red) or \x1b[1;31m (bold red)
|
||||
// The color codes should be different (green vs red)
|
||||
// Valid commands should have \x1b[32m (green)
|
||||
// Invalid commands should have \x1b[31m (red) or \x1b[1;31m (bold red)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_preserves_text_content() {
|
||||
let input = "echo hello world";
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input(input);
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
let input = "echo hello world";
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input(input);
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
|
||||
// Remove ANSI codes to check text content
|
||||
let text_only: String = output.chars()
|
||||
.filter(|c| !c.is_control() && *c != '\x1b')
|
||||
.collect();
|
||||
// Remove ANSI codes to check text content
|
||||
let text_only: String = output
|
||||
.chars()
|
||||
.filter(|c| !c.is_control() && *c != '\x1b')
|
||||
.collect();
|
||||
|
||||
// Should still contain the words (might have escape sequence fragments)
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
assert!(output.contains("world"));
|
||||
// Should still contain the words (might have escape sequence fragments)
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
assert!(output.contains("world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_multiple_tokens() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("ls -la | grep foo");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input("ls -la | grep foo");
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
|
||||
// Should contain all tokens
|
||||
assert!(output.contains("ls"));
|
||||
assert!(output.contains("-la"));
|
||||
assert!(output.contains("|"));
|
||||
assert!(output.contains("grep"));
|
||||
assert!(output.contains("foo"));
|
||||
// Should contain all tokens
|
||||
assert!(output.contains("ls"));
|
||||
assert!(output.contains("-la"));
|
||||
assert!(output.contains("|"));
|
||||
assert!(output.contains("grep"));
|
||||
assert!(output.contains("foo"));
|
||||
|
||||
// Should have ANSI codes
|
||||
assert!(output.contains("\x1b["));
|
||||
// Should have ANSI codes
|
||||
assert!(output.contains("\x1b["));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_string_with_variable() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input(r#"echo "hello $USER""#);
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
let mut highlighter = Highlighter::new();
|
||||
highlighter.load_input(r#"echo "hello $USER""#);
|
||||
highlighter.highlight();
|
||||
let output = highlighter.take();
|
||||
|
||||
// Should contain the text
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
assert!(output.contains("USER"));
|
||||
// Should contain the text
|
||||
assert!(output.contains("echo"));
|
||||
assert!(output.contains("hello"));
|
||||
assert!(output.contains("USER"));
|
||||
|
||||
// Should have ANSI codes for different elements
|
||||
assert!(output.contains("\x1b["));
|
||||
// Should have ANSI codes for different elements
|
||||
assert!(output.contains("\x1b["));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighter_reusable() {
|
||||
let mut highlighter = Highlighter::new();
|
||||
let mut highlighter = Highlighter::new();
|
||||
|
||||
// First input
|
||||
highlighter.load_input("echo first");
|
||||
highlighter.highlight();
|
||||
let output1 = highlighter.take();
|
||||
// First input
|
||||
highlighter.load_input("echo first");
|
||||
highlighter.highlight();
|
||||
let output1 = highlighter.take();
|
||||
|
||||
// Second input (reusing same highlighter)
|
||||
highlighter.load_input("echo second");
|
||||
highlighter.highlight();
|
||||
let output2 = highlighter.take();
|
||||
// Second input (reusing same highlighter)
|
||||
highlighter.load_input("echo second");
|
||||
highlighter.highlight();
|
||||
let output2 = highlighter.take();
|
||||
|
||||
// Both should work
|
||||
assert!(output1.contains("first"));
|
||||
assert!(output2.contains("second"));
|
||||
// Both should work
|
||||
assert!(output1.contains("first"));
|
||||
assert!(output2.contains("second"));
|
||||
|
||||
// Should not contain each other's text
|
||||
assert!(!output1.contains("second"));
|
||||
assert!(!output2.contains("first"));
|
||||
// Should not contain each other's text
|
||||
assert!(!output1.contains("second"));
|
||||
assert!(!output2.contains("first"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -474,133 +498,143 @@ fn highlighter_reusable() {
|
||||
|
||||
#[test]
|
||||
fn annotate_unclosed_string() {
|
||||
let input = r#"echo "hello"#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#"echo "hello"#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should handle unclosed string gracefully
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
// May or may not have STRING_DQ_END depending on implementation
|
||||
// Should handle unclosed string gracefully
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
// May or may not have STRING_DQ_END depending on implementation
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_unclosed_command_sub() {
|
||||
let input = "echo $(whoami";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo $(whoami";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should handle unclosed command sub gracefully
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
// Should handle unclosed command sub gracefully
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_empty_command_sub() {
|
||||
let input = "echo $()";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "echo $()";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should handle empty command sub
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
// Should handle empty command sub
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB_END));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_escaped_characters() {
|
||||
let input = r#"echo \$foo \`bar\` \"test\""#;
|
||||
let annotated = annotate_input(input);
|
||||
let input = r#"echo \$foo \`bar\` \"test\""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should not mark escaped $ as variable
|
||||
// This is tricky - the behavior depends on implementation
|
||||
// At minimum, should not crash
|
||||
// Should not mark escaped $ as variable
|
||||
// This is tricky - the behavior depends on implementation
|
||||
// At minimum, should not crash
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_special_variables() {
|
||||
let input = "echo $0 $1 $2 $3 $4";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo $0 $1 $2 $3 $4";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should mark positional parameters
|
||||
let var_count = annotated.chars().filter(|&c| c == markers::VAR_SUB).count();
|
||||
assert!(var_count >= 5, "Expected at least 5 VAR_SUB markers, found {}", var_count);
|
||||
// Should mark positional parameters
|
||||
let var_count = annotated.chars().filter(|&c| c == markers::VAR_SUB).count();
|
||||
assert!(
|
||||
var_count >= 5,
|
||||
"Expected at least 5 VAR_SUB markers, found {}",
|
||||
var_count
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_variable_no_expansion_in_single_quotes() {
|
||||
let input = "echo '$foo'";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo '$foo'";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
|
||||
// Should NOT have VAR_SUB markers (variables don't expand in single quotes)
|
||||
// Note: The annotator might still mark it - depends on implementation
|
||||
// Should NOT have VAR_SUB markers (variables don't expand in single quotes)
|
||||
// Note: The annotator might still mark it - depends on implementation
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_complex_pipeline() {
|
||||
let input = "cat file.txt | grep pattern | sed 's/foo/bar/' | sort | uniq";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "cat file.txt | grep pattern | sed 's/foo/bar/' | sort | uniq";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have multiple OPERATOR markers for pipes
|
||||
let operator_count = annotated.chars().filter(|&c| c == markers::OPERATOR).count();
|
||||
assert!(operator_count >= 4);
|
||||
// Should have multiple OPERATOR markers for pipes
|
||||
let operator_count = annotated
|
||||
.chars()
|
||||
.filter(|&c| c == markers::OPERATOR)
|
||||
.count();
|
||||
assert!(operator_count >= 4);
|
||||
|
||||
// Should have multiple COMMAND markers
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert!(command_count >= 5);
|
||||
// Should have multiple COMMAND markers
|
||||
let command_count = annotated.chars().filter(|&c| c == markers::COMMAND).count();
|
||||
assert!(command_count >= 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_assignment_with_command_sub() {
|
||||
let input = "FOO=$(whoami)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
let input = "FOO=$(whoami)";
|
||||
let annotated = annotate_input_recursive(input);
|
||||
|
||||
// Should have ASSIGNMENT marker
|
||||
assert!(has_marker(&annotated, markers::ASSIGNMENT));
|
||||
// Should have ASSIGNMENT marker
|
||||
assert!(has_marker(&annotated, markers::ASSIGNMENT));
|
||||
|
||||
// Should have CMD_SUB marker
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
// Should have CMD_SUB marker
|
||||
assert!(has_marker(&annotated, markers::CMD_SUB));
|
||||
|
||||
// Inside command sub should have COMMAND marker
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// Inside command sub should have COMMAND marker
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_redirect_with_fd() {
|
||||
let input = "command 2>&1";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "command 2>&1";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have REDIRECT marker for the redirect operator
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
// Should have REDIRECT marker for the redirect operator
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_multiple_redirects() {
|
||||
let input = "command > out.txt 2>&1";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "command > out.txt 2>&1";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have multiple REDIRECT markers
|
||||
let redirect_count = annotated.chars().filter(|&c| c == markers::REDIRECT).count();
|
||||
assert!(redirect_count >= 2);
|
||||
// Should have multiple REDIRECT markers
|
||||
let redirect_count = annotated
|
||||
.chars()
|
||||
.filter(|&c| c == markers::REDIRECT)
|
||||
.count();
|
||||
assert!(redirect_count >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_here_string() {
|
||||
let input = "cat <<< 'hello world'";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "cat <<< 'hello world'";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should have REDIRECT marker for <<<
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
// Should have REDIRECT marker for <<<
|
||||
assert!(has_marker(&annotated, markers::REDIRECT));
|
||||
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
// Should have STRING_SQ markers
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotate_unicode_content() {
|
||||
let input = "echo 'hello 世界 🌍'";
|
||||
let annotated = annotate_input(input);
|
||||
let input = "echo 'hello 世界 🌍'";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should handle unicode gracefully
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
// Should handle unicode gracefully
|
||||
assert!(has_marker(&annotated, markers::BUILTIN));
|
||||
assert!(has_marker(&annotated, markers::STRING_SQ));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -609,26 +643,26 @@ fn annotate_unicode_content() {
|
||||
|
||||
#[test]
|
||||
fn regression_arg_marker_at_position_zero() {
|
||||
// Regression test: ARG marker was appearing at position 3 for input "ech"
|
||||
// This was caused by SOI/EOI tokens falling through to ARG annotation
|
||||
let input = "ech";
|
||||
let annotated = annotate_input(input);
|
||||
// Regression test: ARG marker was appearing at position 3 for input "ech"
|
||||
// This was caused by SOI/EOI tokens falling through to ARG annotation
|
||||
let input = "ech";
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// Should only have COMMAND marker, not ARG
|
||||
// (incomplete command should still be marked as command attempt)
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
// Should only have COMMAND marker, not ARG
|
||||
// (incomplete command should still be marked as command attempt)
|
||||
assert!(has_marker(&annotated, markers::COMMAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_string_color_in_annotated_strings() {
|
||||
// Regression test: ARG marker was overriding STRING_DQ color
|
||||
let input = r#"echo "test""#;
|
||||
let annotated = annotate_input(input);
|
||||
// Regression test: ARG marker was overriding STRING_DQ color
|
||||
let input = r#"echo "test""#;
|
||||
let annotated = annotate_input(input);
|
||||
|
||||
// STRING_DQ should be present and properly positioned
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ_END));
|
||||
// STRING_DQ should be present and properly positioned
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ));
|
||||
assert!(has_marker(&annotated, markers::STRING_DQ_END));
|
||||
|
||||
// The string markers should come after the ARG marker
|
||||
// (so they override it in the highlighting)
|
||||
// The string markers should come after the ARG marker
|
||||
// (so they override it in the highlighting)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ use super::*;
|
||||
use crate::expand::{expand_aliases, unescape_str};
|
||||
use crate::libsh::error::{Note, ShErr, ShErrKind};
|
||||
use crate::parse::{
|
||||
NdRule, Node, ParseStream,
|
||||
lex::{LexFlags, LexStream, Tk, TkRule},
|
||||
node_operation, NdRule, Node, ParseStream,
|
||||
node_operation,
|
||||
};
|
||||
use crate::state::{write_logic, write_vars};
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
libsh::{error::ShErr, term::{Style, Styled}},
|
||||
libsh::{
|
||||
error::ShErr,
|
||||
term::{Style, Styled},
|
||||
},
|
||||
prompt::readline::{
|
||||
FernVi,
|
||||
history::History,
|
||||
keys::{KeyCode, KeyEvent, ModKeys},
|
||||
linebuf::LineBuf,
|
||||
term::{raw_mode, KeyReader, LineWriter},
|
||||
term::{KeyReader, LineWriter, raw_mode},
|
||||
vimode::{ViInsert, ViMode, ViNormal},
|
||||
FernVi,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -173,8 +176,9 @@ impl LineWriter for TestWriter {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: FernVi structure has changed significantly and readline() method no longer exists
|
||||
// These test helpers are disabled until they can be properly updated
|
||||
// NOTE: FernVi structure has changed significantly and readline() method no
|
||||
// longer exists These test helpers are disabled until they can be properly
|
||||
// updated
|
||||
/*
|
||||
impl FernVi {
|
||||
pub fn new_test(prompt: Option<String>, input: &str, initial: &str) -> Self {
|
||||
@@ -612,10 +616,10 @@ fn fernvi_test_mode_change() {
|
||||
#[test]
|
||||
fn fernvi_test_lorem_ipsum_1() {
|
||||
assert_eq!(fernvi_test(
|
||||
"\x1bwwwwwwww5dWdBdBjjdwjdwbbbcwasdasdasdasd\x1b\r",
|
||||
LOREM_IPSUM),
|
||||
"Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in repin voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur asdasdasdasd occaecat cupinon proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."
|
||||
)
|
||||
"\x1bwwwwwwww5dWdBdBjjdwjdwbbbcwasdasdasdasd\x1b\r",
|
||||
LOREM_IPSUM),
|
||||
"Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in repin voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur asdasdasdasd occaecat cupinon proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -632,9 +636,9 @@ fn fernvi_test_lorem_ipsum_undo() {
|
||||
#[test]
|
||||
fn fernvi_test_lorem_ipsum_ctrl_w() {
|
||||
assert_eq!(fernvi_test(
|
||||
"\x1bj5wiasdasdkjhaksjdhkajshd\x17wordswordswords\x17somemorewords\x17\x1b[D\x1b[D\x17\x1b\r",
|
||||
LOREM_IPSUM),
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim am, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."
|
||||
)
|
||||
"\x1bj5wiasdasdkjhaksjdhkajshd\x17wordswordswords\x17somemorewords\x17\x1b[D\x1b[D\x17\x1b\r",
|
||||
LOREM_IPSUM),
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim am, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::parse::{
|
||||
lex::{LexFlags, LexStream},
|
||||
Node, NdRule, ParseStream, RedirType, Redir,
|
||||
NdRule, Node, ParseStream, Redir, RedirType,
|
||||
lex::{LexFlags, LexStream},
|
||||
};
|
||||
use crate::procio::{IoFrame, IoMode, IoStack};
|
||||
|
||||
@@ -11,187 +11,238 @@ use crate::procio::{IoFrame, IoMode, IoStack};
|
||||
// ============================================================================
|
||||
|
||||
fn parse_command(input: &str) -> Node {
|
||||
let source = Arc::new(input.to_string());
|
||||
let tokens = LexStream::new(source, LexFlags::empty())
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let source = Arc::new(input.to_string());
|
||||
let tokens = LexStream::new(source, LexFlags::empty())
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut nodes = ParseStream::new(tokens)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let mut nodes = ParseStream::new(tokens).flatten().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(nodes.len(), 1, "Expected exactly one node");
|
||||
let top_node = nodes.remove(0);
|
||||
assert_eq!(nodes.len(), 1, "Expected exactly one node");
|
||||
let top_node = nodes.remove(0);
|
||||
|
||||
// Navigate to the actual Command node within the AST structure
|
||||
// Structure is typically: Conjunction -> Pipeline -> Command
|
||||
match top_node.class {
|
||||
NdRule::Conjunction { elements } => {
|
||||
let first_element = elements.into_iter().next().expect("Expected at least one conjunction element");
|
||||
match first_element.cmd.class {
|
||||
NdRule::Pipeline { cmds, .. } => {
|
||||
let mut commands = cmds;
|
||||
assert_eq!(commands.len(), 1, "Expected exactly one command in pipeline");
|
||||
commands.remove(0)
|
||||
}
|
||||
NdRule::Command { .. } => *first_element.cmd,
|
||||
_ => panic!("Expected Command or Pipeline node, got {:?}", first_element.cmd.class),
|
||||
}
|
||||
}
|
||||
NdRule::Pipeline { cmds, .. } => {
|
||||
let mut commands = cmds;
|
||||
assert_eq!(commands.len(), 1, "Expected exactly one command in pipeline");
|
||||
commands.remove(0)
|
||||
}
|
||||
NdRule::Command { .. } => top_node,
|
||||
_ => panic!("Expected Conjunction, Pipeline, or Command node, got {:?}", top_node.class),
|
||||
}
|
||||
// Navigate to the actual Command node within the AST structure
|
||||
// Structure is typically: Conjunction -> Pipeline -> Command
|
||||
match top_node.class {
|
||||
NdRule::Conjunction { elements } => {
|
||||
let first_element = elements
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected at least one conjunction element");
|
||||
match first_element.cmd.class {
|
||||
NdRule::Pipeline { cmds, .. } => {
|
||||
let mut commands = cmds;
|
||||
assert_eq!(
|
||||
commands.len(),
|
||||
1,
|
||||
"Expected exactly one command in pipeline"
|
||||
);
|
||||
commands.remove(0)
|
||||
}
|
||||
NdRule::Command { .. } => *first_element.cmd,
|
||||
_ => panic!(
|
||||
"Expected Command or Pipeline node, got {:?}",
|
||||
first_element.cmd.class
|
||||
),
|
||||
}
|
||||
}
|
||||
NdRule::Pipeline { cmds, .. } => {
|
||||
let mut commands = cmds;
|
||||
assert_eq!(
|
||||
commands.len(),
|
||||
1,
|
||||
"Expected exactly one command in pipeline"
|
||||
);
|
||||
commands.remove(0)
|
||||
}
|
||||
NdRule::Command { .. } => top_node,
|
||||
_ => panic!(
|
||||
"Expected Conjunction, Pipeline, or Command node, got {:?}",
|
||||
top_node.class
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_output_redirect() {
|
||||
let node = parse_command("echo hello > output.txt");
|
||||
let node = parse_command("echo hello > output.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_append_redirect() {
|
||||
let node = parse_command("echo hello >> output.txt");
|
||||
let node = parse_command("echo hello >> output.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::Append));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
assert!(matches!(redir.class, RedirType::Append));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_input_redirect() {
|
||||
let node = parse_command("cat < input.txt");
|
||||
let node = parse_command("cat < input.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::Input));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 0, .. }));
|
||||
assert!(matches!(redir.class, RedirType::Input));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 0, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_stderr_redirect() {
|
||||
let node = parse_command("ls 2> errors.txt");
|
||||
let node = parse_command("ls 2> errors.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 2, .. }));
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 2, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_stderr_to_stdout() {
|
||||
let node = parse_command("ls 2>&1");
|
||||
let node = parse_command("ls 2>&1");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.io_mode, IoMode::Fd { tgt_fd: 2, src_fd: 1 }));
|
||||
assert!(matches!(
|
||||
redir.io_mode,
|
||||
IoMode::Fd {
|
||||
tgt_fd: 2,
|
||||
src_fd: 1
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_stdout_to_stderr() {
|
||||
let node = parse_command("echo test 1>&2");
|
||||
let node = parse_command("echo test 1>&2");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.io_mode, IoMode::Fd { tgt_fd: 1, src_fd: 2 }));
|
||||
assert!(matches!(
|
||||
redir.io_mode,
|
||||
IoMode::Fd {
|
||||
tgt_fd: 1,
|
||||
src_fd: 2
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multiple_redirects() {
|
||||
let node = parse_command("cmd < input.txt > output.txt 2> errors.txt");
|
||||
let node = parse_command("cmd < input.txt > output.txt 2> errors.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 3);
|
||||
assert_eq!(node.redirs.len(), 3);
|
||||
|
||||
// Input redirect
|
||||
assert!(matches!(node.redirs[0].class, RedirType::Input));
|
||||
assert!(matches!(node.redirs[0].io_mode, IoMode::File { tgt_fd: 0, .. }));
|
||||
// Input redirect
|
||||
assert!(matches!(node.redirs[0].class, RedirType::Input));
|
||||
assert!(matches!(
|
||||
node.redirs[0].io_mode,
|
||||
IoMode::File { tgt_fd: 0, .. }
|
||||
));
|
||||
|
||||
// Stdout redirect
|
||||
assert!(matches!(node.redirs[1].class, RedirType::Output));
|
||||
assert!(matches!(node.redirs[1].io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
// Stdout redirect
|
||||
assert!(matches!(node.redirs[1].class, RedirType::Output));
|
||||
assert!(matches!(
|
||||
node.redirs[1].io_mode,
|
||||
IoMode::File { tgt_fd: 1, .. }
|
||||
));
|
||||
|
||||
// Stderr redirect
|
||||
assert!(matches!(node.redirs[2].class, RedirType::Output));
|
||||
assert!(matches!(node.redirs[2].io_mode, IoMode::File { tgt_fd: 2, .. }));
|
||||
// Stderr redirect
|
||||
assert!(matches!(node.redirs[2].class, RedirType::Output));
|
||||
assert!(matches!(
|
||||
node.redirs[2].io_mode,
|
||||
IoMode::File { tgt_fd: 2, .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_custom_fd_redirect() {
|
||||
let node = parse_command("echo test 3> fd3.txt");
|
||||
let node = parse_command("echo test 3> fd3.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 3, .. }));
|
||||
assert!(matches!(redir.class, RedirType::Output));
|
||||
assert!(matches!(redir.io_mode, IoMode::File { tgt_fd: 3, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_custom_fd_dup() {
|
||||
let node = parse_command("cmd 3>&4");
|
||||
let node = parse_command("cmd 3>&4");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.io_mode, IoMode::Fd { tgt_fd: 3, src_fd: 4 }));
|
||||
assert!(matches!(
|
||||
redir.io_mode,
|
||||
IoMode::Fd {
|
||||
tgt_fd: 3,
|
||||
src_fd: 4
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_heredoc() {
|
||||
let node = parse_command("cat << EOF");
|
||||
let node = parse_command("cat << EOF");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::HereDoc));
|
||||
assert!(matches!(redir.class, RedirType::HereDoc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_herestring() {
|
||||
let node = parse_command("cat <<< 'hello world'");
|
||||
let node = parse_command("cat <<< 'hello world'");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
let redir = &node.redirs[0];
|
||||
|
||||
assert!(matches!(redir.class, RedirType::HereString));
|
||||
assert!(matches!(redir.class, RedirType::HereString));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_redirect_with_no_space() {
|
||||
let node = parse_command("echo hello >output.txt");
|
||||
let node = parse_command("echo hello >output.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
assert!(matches!(node.redirs[0].class, RedirType::Output));
|
||||
assert_eq!(node.redirs.len(), 1);
|
||||
assert!(matches!(node.redirs[0].class, RedirType::Output));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_redirect_order_preserved() {
|
||||
let node = parse_command("cmd 2>&1 > file.txt");
|
||||
let node = parse_command("cmd 2>&1 > file.txt");
|
||||
|
||||
assert_eq!(node.redirs.len(), 2);
|
||||
assert_eq!(node.redirs.len(), 2);
|
||||
|
||||
// First redirect: 2>&1
|
||||
assert!(matches!(node.redirs[0].io_mode, IoMode::Fd { tgt_fd: 2, src_fd: 1 }));
|
||||
// First redirect: 2>&1
|
||||
assert!(matches!(
|
||||
node.redirs[0].io_mode,
|
||||
IoMode::Fd {
|
||||
tgt_fd: 2,
|
||||
src_fd: 1
|
||||
}
|
||||
));
|
||||
|
||||
// Second redirect: > file.txt
|
||||
assert!(matches!(node.redirs[1].class, RedirType::Output));
|
||||
assert!(matches!(node.redirs[1].io_mode, IoMode::File { tgt_fd: 1, .. }));
|
||||
// Second redirect: > file.txt
|
||||
assert!(matches!(node.redirs[1].class, RedirType::Output));
|
||||
assert!(matches!(
|
||||
node.redirs[1].io_mode,
|
||||
IoMode::File { tgt_fd: 1, .. }
|
||||
));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -200,148 +251,148 @@ fn parse_redirect_order_preserved() {
|
||||
|
||||
#[test]
|
||||
fn iostack_new() {
|
||||
let stack = IoStack::new();
|
||||
let stack = IoStack::new();
|
||||
|
||||
assert_eq!(stack.len(), 1, "IoStack should start with one frame");
|
||||
assert_eq!(stack.curr_frame().len(), 0, "Initial frame should be empty");
|
||||
assert_eq!(stack.len(), 1, "IoStack should start with one frame");
|
||||
assert_eq!(stack.curr_frame().len(), 0, "Initial frame should be empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_push_pop_frame() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
// Push a new frame
|
||||
stack.push_frame(IoFrame::new());
|
||||
assert_eq!(stack.len(), 2);
|
||||
// Push a new frame
|
||||
stack.push_frame(IoFrame::new());
|
||||
assert_eq!(stack.len(), 2);
|
||||
|
||||
// Pop it back
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
assert_eq!(stack.len(), 1);
|
||||
// Pop it back
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
assert_eq!(stack.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_never_empties() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
// Try to pop the last frame
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
// Try to pop the last frame
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
|
||||
// Stack should still have one frame
|
||||
assert_eq!(stack.len(), 1);
|
||||
// Stack should still have one frame
|
||||
assert_eq!(stack.len(), 1);
|
||||
|
||||
// Pop again - should still have one frame
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
assert_eq!(stack.len(), 1);
|
||||
// Pop again - should still have one frame
|
||||
let frame = stack.pop_frame();
|
||||
assert_eq!(frame.len(), 0);
|
||||
assert_eq!(stack.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_push_to_frame() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
let redir = crate::parse::Redir::new(
|
||||
IoMode::fd(1, 2),
|
||||
RedirType::Output,
|
||||
);
|
||||
let redir = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
|
||||
stack.push_to_frame(redir);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
stack.push_to_frame(redir);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_append_to_frame() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
let redirs = vec![
|
||||
crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output),
|
||||
crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output),
|
||||
];
|
||||
let redirs = vec![
|
||||
crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output),
|
||||
crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output),
|
||||
];
|
||||
|
||||
stack.append_to_frame(redirs);
|
||||
assert_eq!(stack.curr_frame().len(), 2);
|
||||
stack.append_to_frame(redirs);
|
||||
assert_eq!(stack.curr_frame().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_frame_isolation() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
// Add redir to first frame
|
||||
let redir1 = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
stack.push_to_frame(redir1);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
// Add redir to first frame
|
||||
let redir1 = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
stack.push_to_frame(redir1);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
|
||||
// Push new frame
|
||||
stack.push_frame(IoFrame::new());
|
||||
assert_eq!(stack.curr_frame().len(), 0, "New frame should be empty");
|
||||
// Push new frame
|
||||
stack.push_frame(IoFrame::new());
|
||||
assert_eq!(stack.curr_frame().len(), 0, "New frame should be empty");
|
||||
|
||||
// Add redir to second frame
|
||||
let redir2 = crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output);
|
||||
stack.push_to_frame(redir2);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
// Add redir to second frame
|
||||
let redir2 = crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output);
|
||||
stack.push_to_frame(redir2);
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
|
||||
// Pop second frame
|
||||
let frame2 = stack.pop_frame();
|
||||
assert_eq!(frame2.len(), 1);
|
||||
// Pop second frame
|
||||
let frame2 = stack.pop_frame();
|
||||
assert_eq!(frame2.len(), 1);
|
||||
|
||||
// First frame should still have its redir
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
// First frame should still have its redir
|
||||
assert_eq!(stack.curr_frame().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iostack_flatten() {
|
||||
let mut stack = IoStack::new();
|
||||
let mut stack = IoStack::new();
|
||||
|
||||
// Add redir to first frame
|
||||
let redir1 = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
stack.push_to_frame(redir1);
|
||||
// Add redir to first frame
|
||||
let redir1 = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
stack.push_to_frame(redir1);
|
||||
|
||||
// Push new frame with redir
|
||||
let mut frame2 = IoFrame::new();
|
||||
frame2.push(crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output));
|
||||
stack.push_frame(frame2);
|
||||
// Push new frame with redir
|
||||
let mut frame2 = IoFrame::new();
|
||||
frame2.push(crate::parse::Redir::new(
|
||||
IoMode::fd(2, 1),
|
||||
RedirType::Output,
|
||||
));
|
||||
stack.push_frame(frame2);
|
||||
|
||||
// Push third frame with redir
|
||||
let mut frame3 = IoFrame::new();
|
||||
frame3.push(crate::parse::Redir::new(IoMode::fd(0, 3), RedirType::Input));
|
||||
stack.push_frame(frame3);
|
||||
// Push third frame with redir
|
||||
let mut frame3 = IoFrame::new();
|
||||
frame3.push(crate::parse::Redir::new(IoMode::fd(0, 3), RedirType::Input));
|
||||
stack.push_frame(frame3);
|
||||
|
||||
assert_eq!(stack.len(), 3);
|
||||
assert_eq!(stack.len(), 3);
|
||||
|
||||
// Flatten
|
||||
stack.flatten();
|
||||
// Flatten
|
||||
stack.flatten();
|
||||
|
||||
// Should have one frame with all redirects
|
||||
assert_eq!(stack.len(), 1);
|
||||
assert_eq!(stack.curr_frame().len(), 3);
|
||||
// Should have one frame with all redirects
|
||||
assert_eq!(stack.len(), 1);
|
||||
assert_eq!(stack.curr_frame().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioframe_new() {
|
||||
let frame = IoFrame::new();
|
||||
assert_eq!(frame.len(), 0);
|
||||
let frame = IoFrame::new();
|
||||
assert_eq!(frame.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioframe_from_redirs() {
|
||||
let redirs = vec![
|
||||
crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output),
|
||||
crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output),
|
||||
];
|
||||
let redirs = vec![
|
||||
crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output),
|
||||
crate::parse::Redir::new(IoMode::fd(2, 1), RedirType::Output),
|
||||
];
|
||||
|
||||
let frame = IoFrame::from_redirs(redirs);
|
||||
assert_eq!(frame.len(), 2);
|
||||
let frame = IoFrame::from_redirs(redirs);
|
||||
assert_eq!(frame.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioframe_push() {
|
||||
let mut frame = IoFrame::new();
|
||||
let mut frame = IoFrame::new();
|
||||
|
||||
let redir = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
frame.push(redir);
|
||||
let redir = crate::parse::Redir::new(IoMode::fd(1, 2), RedirType::Output);
|
||||
frame.push(redir);
|
||||
|
||||
assert_eq!(frame.len(), 1);
|
||||
assert_eq!(frame.len(), 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -350,28 +401,28 @@ fn ioframe_push() {
|
||||
|
||||
#[test]
|
||||
fn iomode_fd_construction() {
|
||||
let io_mode = IoMode::fd(2, 1);
|
||||
let io_mode = IoMode::fd(2, 1);
|
||||
|
||||
match io_mode {
|
||||
IoMode::Fd { tgt_fd, src_fd } => {
|
||||
assert_eq!(tgt_fd, 2);
|
||||
assert_eq!(src_fd, 1);
|
||||
}
|
||||
_ => panic!("Expected IoMode::Fd"),
|
||||
}
|
||||
match io_mode {
|
||||
IoMode::Fd { tgt_fd, src_fd } => {
|
||||
assert_eq!(tgt_fd, 2);
|
||||
assert_eq!(src_fd, 1);
|
||||
}
|
||||
_ => panic!("Expected IoMode::Fd"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iomode_tgt_fd() {
|
||||
let fd_mode = IoMode::fd(2, 1);
|
||||
assert_eq!(fd_mode.tgt_fd(), 2);
|
||||
let fd_mode = IoMode::fd(2, 1);
|
||||
assert_eq!(fd_mode.tgt_fd(), 2);
|
||||
|
||||
let file_mode = IoMode::file(1, std::path::PathBuf::from("test.txt"), RedirType::Output);
|
||||
assert_eq!(file_mode.tgt_fd(), 1);
|
||||
let file_mode = IoMode::file(1, std::path::PathBuf::from("test.txt"), RedirType::Output);
|
||||
assert_eq!(file_mode.tgt_fd(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iomode_src_fd() {
|
||||
let fd_mode = IoMode::fd(2, 1);
|
||||
assert_eq!(fd_mode.src_fd(), 1);
|
||||
let fd_mode = IoMode::fd(2, 1);
|
||||
assert_eq!(fd_mode.src_fd(), 1);
|
||||
}
|
||||
|
||||
@@ -6,264 +6,280 @@ use crate::state::{LogTab, ScopeStack, ShellParam, VarFlags, VarTab};
|
||||
|
||||
#[test]
|
||||
fn scopestack_new() {
|
||||
let stack = ScopeStack::new();
|
||||
let stack = ScopeStack::new();
|
||||
|
||||
// Should start with one global scope
|
||||
assert!(stack.var_exists("PATH") || !stack.var_exists("PATH")); // Just check it doesn't panic
|
||||
// Should start with one global scope
|
||||
assert!(stack.var_exists("PATH") || !stack.var_exists("PATH")); // Just check
|
||||
// it doesn't
|
||||
// panic
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_descend_ascend() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Set a global variable
|
||||
stack.set_var("GLOBAL", "value1", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
// Set a global variable
|
||||
stack.set_var("GLOBAL", "value1", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
|
||||
// Descend into a new scope
|
||||
stack.descend(None);
|
||||
// Descend into a new scope
|
||||
stack.descend(None);
|
||||
|
||||
// Global should still be visible
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
// Global should still be visible
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
|
||||
// Set a local variable
|
||||
stack.set_var("LOCAL", "value2", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("LOCAL"), "value2");
|
||||
// Set a local variable
|
||||
stack.set_var("LOCAL", "value2", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("LOCAL"), "value2");
|
||||
|
||||
// Ascend back to global scope
|
||||
stack.ascend();
|
||||
// Ascend back to global scope
|
||||
stack.ascend();
|
||||
|
||||
// Global should still exist
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
// Global should still exist
|
||||
assert_eq!(stack.get_var("GLOBAL"), "value1");
|
||||
|
||||
// Local should no longer be visible
|
||||
assert_eq!(stack.get_var("LOCAL"), "");
|
||||
// Local should no longer be visible
|
||||
assert_eq!(stack.get_var("LOCAL"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_variable_shadowing() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Set global variable
|
||||
stack.set_var("VAR", "global", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("VAR"), "global");
|
||||
// Set global variable
|
||||
stack.set_var("VAR", "global", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("VAR"), "global");
|
||||
|
||||
// Descend into local scope
|
||||
stack.descend(None);
|
||||
// Descend into local scope
|
||||
stack.descend(None);
|
||||
|
||||
// Set local variable with same name
|
||||
stack.set_var("VAR", "local", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("VAR"), "local", "Local should shadow global");
|
||||
// Set local variable with same name
|
||||
stack.set_var("VAR", "local", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("VAR"), "local", "Local should shadow global");
|
||||
|
||||
// Ascend back
|
||||
stack.ascend();
|
||||
// Ascend back
|
||||
stack.ascend();
|
||||
|
||||
// Global should be restored
|
||||
assert_eq!(stack.get_var("VAR"), "global", "Global should be unchanged after ascend");
|
||||
// Global should be restored
|
||||
assert_eq!(
|
||||
stack.get_var("VAR"),
|
||||
"global",
|
||||
"Global should be unchanged after ascend"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_local_vs_global_flag() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Descend into a local scope
|
||||
stack.descend(None);
|
||||
// Descend into a local scope
|
||||
stack.descend(None);
|
||||
|
||||
// Set with LOCAL flag - should go in current scope
|
||||
stack.set_var("LOCAL_VAR", "local", VarFlags::LOCAL);
|
||||
// Set with LOCAL flag - should go in current scope
|
||||
stack.set_var("LOCAL_VAR", "local", VarFlags::LOCAL);
|
||||
|
||||
// Set without LOCAL flag - should go in global scope
|
||||
stack.set_var("GLOBAL_VAR", "global", VarFlags::NONE);
|
||||
// Set without LOCAL flag - should go in global scope
|
||||
stack.set_var("GLOBAL_VAR", "global", VarFlags::NONE);
|
||||
|
||||
// Both visible from local scope
|
||||
assert_eq!(stack.get_var("LOCAL_VAR"), "local");
|
||||
assert_eq!(stack.get_var("GLOBAL_VAR"), "global");
|
||||
// Both visible from local scope
|
||||
assert_eq!(stack.get_var("LOCAL_VAR"), "local");
|
||||
assert_eq!(stack.get_var("GLOBAL_VAR"), "global");
|
||||
|
||||
// Ascend to global
|
||||
stack.ascend();
|
||||
// Ascend to global
|
||||
stack.ascend();
|
||||
|
||||
// Only global var should be visible
|
||||
assert_eq!(stack.get_var("GLOBAL_VAR"), "global");
|
||||
assert_eq!(stack.get_var("LOCAL_VAR"), "");
|
||||
// Only global var should be visible
|
||||
assert_eq!(stack.get_var("GLOBAL_VAR"), "global");
|
||||
assert_eq!(stack.get_var("LOCAL_VAR"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_multiple_levels() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
stack.set_var("LEVEL0", "global", VarFlags::NONE);
|
||||
stack.set_var("LEVEL0", "global", VarFlags::NONE);
|
||||
|
||||
// Level 1
|
||||
stack.descend(None);
|
||||
stack.set_var("LEVEL1", "first", VarFlags::LOCAL);
|
||||
// Level 1
|
||||
stack.descend(None);
|
||||
stack.set_var("LEVEL1", "first", VarFlags::LOCAL);
|
||||
|
||||
// Level 2
|
||||
stack.descend(None);
|
||||
stack.set_var("LEVEL2", "second", VarFlags::LOCAL);
|
||||
// Level 2
|
||||
stack.descend(None);
|
||||
stack.set_var("LEVEL2", "second", VarFlags::LOCAL);
|
||||
|
||||
// All variables visible from deepest scope
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "first");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "second");
|
||||
// All variables visible from deepest scope
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "first");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "second");
|
||||
|
||||
// Ascend to level 1
|
||||
stack.ascend();
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "first");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "");
|
||||
// Ascend to level 1
|
||||
stack.ascend();
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "first");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "");
|
||||
|
||||
// Ascend to global
|
||||
stack.ascend();
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "");
|
||||
// Ascend to global
|
||||
stack.ascend();
|
||||
assert_eq!(stack.get_var("LEVEL0"), "global");
|
||||
assert_eq!(stack.get_var("LEVEL1"), "");
|
||||
assert_eq!(stack.get_var("LEVEL2"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_cannot_ascend_past_global() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
|
||||
// Try to ascend from global scope (should be no-op)
|
||||
stack.ascend();
|
||||
stack.ascend();
|
||||
stack.ascend();
|
||||
// Try to ascend from global scope (should be no-op)
|
||||
stack.ascend();
|
||||
stack.ascend();
|
||||
stack.ascend();
|
||||
|
||||
// Variable should still exist
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
// Variable should still exist
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_descend_with_args() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Get initial param values from global scope (test process args)
|
||||
let global_param_1 = stack.get_param(ShellParam::Pos(1));
|
||||
// Get initial param values from global scope (test process args)
|
||||
let global_param_1 = stack.get_param(ShellParam::Pos(1));
|
||||
|
||||
// Descend with positional parameters
|
||||
let args = vec!["local_arg1".to_string(), "local_arg2".to_string()];
|
||||
stack.descend(Some(args));
|
||||
// Descend with positional parameters
|
||||
let args = vec!["local_arg1".to_string(), "local_arg2".to_string()];
|
||||
stack.descend(Some(args));
|
||||
|
||||
// In local scope, positional params come from the VarTab created during descend
|
||||
// VarTab::new() initializes with process args, then our args are appended
|
||||
// So we check that SOME positional parameter exists (implementation detail may vary)
|
||||
let local_param = stack.get_param(ShellParam::Pos(1));
|
||||
assert!(!local_param.is_empty(), "Should have positional parameters in local scope");
|
||||
// In local scope, positional params come from the VarTab created during descend
|
||||
// VarTab::new() initializes with process args, then our args are appended
|
||||
// So we check that SOME positional parameter exists (implementation detail may
|
||||
// vary)
|
||||
let local_param = stack.get_param(ShellParam::Pos(1));
|
||||
assert!(
|
||||
!local_param.is_empty(),
|
||||
"Should have positional parameters in local scope"
|
||||
);
|
||||
|
||||
// Ascend back
|
||||
stack.ascend();
|
||||
// Ascend back
|
||||
stack.ascend();
|
||||
|
||||
// Should be back to global scope parameters
|
||||
assert_eq!(stack.get_param(ShellParam::Pos(1)), global_param_1);
|
||||
// Should be back to global scope parameters
|
||||
assert_eq!(stack.get_param(ShellParam::Pos(1)), global_param_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_global_parameters() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Set global parameters
|
||||
stack.set_param(ShellParam::Status, "0");
|
||||
stack.set_param(ShellParam::LastJob, "1234");
|
||||
// Set global parameters
|
||||
stack.set_param(ShellParam::Status, "0");
|
||||
stack.set_param(ShellParam::LastJob, "1234");
|
||||
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "0");
|
||||
assert_eq!(stack.get_param(ShellParam::LastJob), "1234");
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "0");
|
||||
assert_eq!(stack.get_param(ShellParam::LastJob), "1234");
|
||||
|
||||
// Descend into local scope
|
||||
stack.descend(None);
|
||||
// Descend into local scope
|
||||
stack.descend(None);
|
||||
|
||||
// Global parameters should still be visible
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "0");
|
||||
assert_eq!(stack.get_param(ShellParam::LastJob), "1234");
|
||||
// Global parameters should still be visible
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "0");
|
||||
assert_eq!(stack.get_param(ShellParam::LastJob), "1234");
|
||||
|
||||
// Modify global parameter from local scope
|
||||
stack.set_param(ShellParam::Status, "1");
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "1");
|
||||
// Modify global parameter from local scope
|
||||
stack.set_param(ShellParam::Status, "1");
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "1");
|
||||
|
||||
// Ascend
|
||||
stack.ascend();
|
||||
// Ascend
|
||||
stack.ascend();
|
||||
|
||||
// Global parameter should retain modified value
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "1");
|
||||
// Global parameter should retain modified value
|
||||
assert_eq!(stack.get_param(ShellParam::Status), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_unset_var() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
|
||||
stack.unset_var("VAR");
|
||||
assert_eq!(stack.get_var("VAR"), "");
|
||||
assert!(!stack.var_exists("VAR"));
|
||||
stack.unset_var("VAR");
|
||||
assert_eq!(stack.get_var("VAR"), "");
|
||||
assert!(!stack.var_exists("VAR"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_unset_finds_innermost() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
// Set global
|
||||
stack.set_var("VAR", "global", VarFlags::NONE);
|
||||
// Set global
|
||||
stack.set_var("VAR", "global", VarFlags::NONE);
|
||||
|
||||
// Descend and shadow
|
||||
stack.descend(None);
|
||||
stack.set_var("VAR", "local", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("VAR"), "local");
|
||||
// Descend and shadow
|
||||
stack.descend(None);
|
||||
stack.set_var("VAR", "local", VarFlags::LOCAL);
|
||||
assert_eq!(stack.get_var("VAR"), "local");
|
||||
|
||||
// Unset should remove local, revealing global
|
||||
stack.unset_var("VAR");
|
||||
assert_eq!(stack.get_var("VAR"), "global");
|
||||
// Unset should remove local, revealing global
|
||||
stack.unset_var("VAR");
|
||||
assert_eq!(stack.get_var("VAR"), "global");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_export_var() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
stack.set_var("VAR", "value", VarFlags::NONE);
|
||||
|
||||
// Export the variable
|
||||
stack.export_var("VAR");
|
||||
// Export the variable
|
||||
stack.export_var("VAR");
|
||||
|
||||
// Variable should still be accessible (flag is internal detail)
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
// Variable should still be accessible (flag is internal detail)
|
||||
assert_eq!(stack.get_var("VAR"), "value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_var_exists() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
assert!(!stack.var_exists("NONEXISTENT"));
|
||||
assert!(!stack.var_exists("NONEXISTENT"));
|
||||
|
||||
stack.set_var("EXISTS", "yes", VarFlags::NONE);
|
||||
assert!(stack.var_exists("EXISTS"));
|
||||
stack.set_var("EXISTS", "yes", VarFlags::NONE);
|
||||
assert!(stack.var_exists("EXISTS"));
|
||||
|
||||
stack.descend(None);
|
||||
assert!(stack.var_exists("EXISTS"), "Global var should be visible in local scope");
|
||||
stack.descend(None);
|
||||
assert!(
|
||||
stack.var_exists("EXISTS"),
|
||||
"Global var should be visible in local scope"
|
||||
);
|
||||
|
||||
stack.set_var("LOCAL", "yes", VarFlags::LOCAL);
|
||||
assert!(stack.var_exists("LOCAL"));
|
||||
stack.set_var("LOCAL", "yes", VarFlags::LOCAL);
|
||||
assert!(stack.var_exists("LOCAL"));
|
||||
|
||||
stack.ascend();
|
||||
assert!(!stack.var_exists("LOCAL"), "Local var should not exist after ascend");
|
||||
stack.ascend();
|
||||
assert!(
|
||||
!stack.var_exists("LOCAL"),
|
||||
"Local var should not exist after ascend"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopestack_flatten_vars() {
|
||||
let mut stack = ScopeStack::new();
|
||||
let mut stack = ScopeStack::new();
|
||||
|
||||
stack.set_var("GLOBAL1", "g1", VarFlags::NONE);
|
||||
stack.set_var("GLOBAL2", "g2", VarFlags::NONE);
|
||||
stack.set_var("GLOBAL1", "g1", VarFlags::NONE);
|
||||
stack.set_var("GLOBAL2", "g2", VarFlags::NONE);
|
||||
|
||||
stack.descend(None);
|
||||
stack.set_var("LOCAL1", "l1", VarFlags::LOCAL);
|
||||
stack.descend(None);
|
||||
stack.set_var("LOCAL1", "l1", VarFlags::LOCAL);
|
||||
|
||||
let flattened = stack.flatten_vars();
|
||||
let flattened = stack.flatten_vars();
|
||||
|
||||
// Should contain variables from all scopes
|
||||
assert!(flattened.contains_key("GLOBAL1"));
|
||||
assert!(flattened.contains_key("GLOBAL2"));
|
||||
assert!(flattened.contains_key("LOCAL1"));
|
||||
// Should contain variables from all scopes
|
||||
assert!(flattened.contains_key("GLOBAL1"));
|
||||
assert!(flattened.contains_key("GLOBAL2"));
|
||||
assert!(flattened.contains_key("LOCAL1"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -272,78 +288,81 @@ fn scopestack_flatten_vars() {
|
||||
|
||||
#[test]
|
||||
fn logtab_new() {
|
||||
let logtab = LogTab::new();
|
||||
assert_eq!(logtab.funcs().len(), 0);
|
||||
assert_eq!(logtab.aliases().len(), 0);
|
||||
let logtab = LogTab::new();
|
||||
assert_eq!(logtab.funcs().len(), 0);
|
||||
assert_eq!(logtab.aliases().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logtab_insert_get_alias() {
|
||||
let mut logtab = LogTab::new();
|
||||
let mut logtab = LogTab::new();
|
||||
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
assert_eq!(logtab.get_alias("nonexistent"), None);
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
assert_eq!(logtab.get_alias("nonexistent"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logtab_overwrite_alias() {
|
||||
let mut logtab = LogTab::new();
|
||||
let mut logtab = LogTab::new();
|
||||
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
|
||||
logtab.insert_alias("ll", "ls -lah");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -lah".to_string()));
|
||||
logtab.insert_alias("ll", "ls -lah");
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -lah".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logtab_remove_alias() {
|
||||
let mut logtab = LogTab::new();
|
||||
let mut logtab = LogTab::new();
|
||||
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert!(logtab.get_alias("ll").is_some());
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
assert!(logtab.get_alias("ll").is_some());
|
||||
|
||||
logtab.remove_alias("ll");
|
||||
assert!(logtab.get_alias("ll").is_none());
|
||||
logtab.remove_alias("ll");
|
||||
assert!(logtab.get_alias("ll").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logtab_clear_aliases() {
|
||||
let mut logtab = LogTab::new();
|
||||
let mut logtab = LogTab::new();
|
||||
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
logtab.insert_alias("la", "ls -A");
|
||||
logtab.insert_alias("l", "ls -CF");
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
logtab.insert_alias("la", "ls -A");
|
||||
logtab.insert_alias("l", "ls -CF");
|
||||
|
||||
assert_eq!(logtab.aliases().len(), 3);
|
||||
assert_eq!(logtab.aliases().len(), 3);
|
||||
|
||||
logtab.clear_aliases();
|
||||
assert_eq!(logtab.aliases().len(), 0);
|
||||
logtab.clear_aliases();
|
||||
assert_eq!(logtab.aliases().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logtab_multiple_aliases() {
|
||||
let mut logtab = LogTab::new();
|
||||
let mut logtab = LogTab::new();
|
||||
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
logtab.insert_alias("la", "ls -A");
|
||||
logtab.insert_alias("grep", "grep --color=auto");
|
||||
logtab.insert_alias("ll", "ls -la");
|
||||
logtab.insert_alias("la", "ls -A");
|
||||
logtab.insert_alias("grep", "grep --color=auto");
|
||||
|
||||
assert_eq!(logtab.aliases().len(), 3);
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
assert_eq!(logtab.get_alias("la"), Some("ls -A".to_string()));
|
||||
assert_eq!(logtab.get_alias("grep"), Some("grep --color=auto".to_string()));
|
||||
assert_eq!(logtab.aliases().len(), 3);
|
||||
assert_eq!(logtab.get_alias("ll"), Some("ls -la".to_string()));
|
||||
assert_eq!(logtab.get_alias("la"), Some("ls -A".to_string()));
|
||||
assert_eq!(
|
||||
logtab.get_alias("grep"),
|
||||
Some("grep --color=auto".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
// Note: Function tests are limited because ShFunc requires complex setup (parsed AST)
|
||||
// We'll test the basic storage/retrieval mechanics
|
||||
// Note: Function tests are limited because ShFunc requires complex setup
|
||||
// (parsed AST) We'll test the basic storage/retrieval mechanics
|
||||
|
||||
#[test]
|
||||
fn logtab_funcs_empty_initially() {
|
||||
let logtab = LogTab::new();
|
||||
assert_eq!(logtab.funcs().len(), 0);
|
||||
assert!(logtab.get_func("nonexistent").is_none());
|
||||
let logtab = LogTab::new();
|
||||
assert_eq!(logtab.funcs().len(), 0);
|
||||
assert!(logtab.get_func("nonexistent").is_none());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -352,109 +371,112 @@ fn logtab_funcs_empty_initially() {
|
||||
|
||||
#[test]
|
||||
fn vartab_new() {
|
||||
let vartab = VarTab::new();
|
||||
// VarTab initializes with some default params, just check it doesn't panic
|
||||
assert!(vartab.get_var("NONEXISTENT").is_empty());
|
||||
let vartab = VarTab::new();
|
||||
// VarTab initializes with some default params, just check it doesn't panic
|
||||
assert!(vartab.get_var("NONEXISTENT").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_set_get_var() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
vartab.set_var("TEST", "value", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("TEST"), "value");
|
||||
vartab.set_var("TEST", "value", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("TEST"), "value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_overwrite_var() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
vartab.set_var("VAR", "value1", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("VAR"), "value1");
|
||||
vartab.set_var("VAR", "value1", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("VAR"), "value1");
|
||||
|
||||
vartab.set_var("VAR", "value2", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("VAR"), "value2");
|
||||
vartab.set_var("VAR", "value2", VarFlags::NONE);
|
||||
assert_eq!(vartab.get_var("VAR"), "value2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_var_exists() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
assert!(!vartab.var_exists("TEST"));
|
||||
assert!(!vartab.var_exists("TEST"));
|
||||
|
||||
vartab.set_var("TEST", "value", VarFlags::NONE);
|
||||
assert!(vartab.var_exists("TEST"));
|
||||
vartab.set_var("TEST", "value", VarFlags::NONE);
|
||||
assert!(vartab.var_exists("TEST"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_unset_var() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
vartab.set_var("VAR", "value", VarFlags::NONE);
|
||||
assert!(vartab.var_exists("VAR"));
|
||||
vartab.set_var("VAR", "value", VarFlags::NONE);
|
||||
assert!(vartab.var_exists("VAR"));
|
||||
|
||||
vartab.unset_var("VAR");
|
||||
assert!(!vartab.var_exists("VAR"));
|
||||
assert_eq!(vartab.get_var("VAR"), "");
|
||||
vartab.unset_var("VAR");
|
||||
assert!(!vartab.var_exists("VAR"));
|
||||
assert_eq!(vartab.get_var("VAR"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_export_var() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
vartab.set_var("VAR", "value", VarFlags::NONE);
|
||||
vartab.export_var("VAR");
|
||||
vartab.set_var("VAR", "value", VarFlags::NONE);
|
||||
vartab.export_var("VAR");
|
||||
|
||||
// Variable should still be accessible
|
||||
assert_eq!(vartab.get_var("VAR"), "value");
|
||||
// Variable should still be accessible
|
||||
assert_eq!(vartab.get_var("VAR"), "value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_positional_params() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
// Get the current argv length
|
||||
let initial_len = vartab.sh_argv().len();
|
||||
// Get the current argv length
|
||||
let initial_len = vartab.sh_argv().len();
|
||||
|
||||
// Clear and reinitialize with known args
|
||||
vartab.clear_args(); // This keeps $0 as current exe
|
||||
// Clear and reinitialize with known args
|
||||
vartab.clear_args(); // This keeps $0 as current exe
|
||||
|
||||
// After clear_args, should have just $0
|
||||
// Push additional args
|
||||
vartab.bpush_arg("test_arg1".to_string());
|
||||
vartab.bpush_arg("test_arg2".to_string());
|
||||
// After clear_args, should have just $0
|
||||
// Push additional args
|
||||
vartab.bpush_arg("test_arg1".to_string());
|
||||
vartab.bpush_arg("test_arg2".to_string());
|
||||
|
||||
// Now sh_argv should be: [exe_path, test_arg1, test_arg2]
|
||||
// Pos(0) = exe_path, Pos(1) = test_arg1, Pos(2) = test_arg2
|
||||
let final_len = vartab.sh_argv().len();
|
||||
assert!(final_len > initial_len || final_len >= 1, "Should have arguments");
|
||||
// Now sh_argv should be: [exe_path, test_arg1, test_arg2]
|
||||
// Pos(0) = exe_path, Pos(1) = test_arg1, Pos(2) = test_arg2
|
||||
let final_len = vartab.sh_argv().len();
|
||||
assert!(
|
||||
final_len > initial_len || final_len >= 1,
|
||||
"Should have arguments"
|
||||
);
|
||||
|
||||
// Just verify we can retrieve the last args we pushed
|
||||
let last_idx = final_len - 1;
|
||||
assert_eq!(vartab.get_param(ShellParam::Pos(last_idx)), "test_arg2");
|
||||
// Just verify we can retrieve the last args we pushed
|
||||
let last_idx = final_len - 1;
|
||||
assert_eq!(vartab.get_param(ShellParam::Pos(last_idx)), "test_arg2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vartab_shell_argv_operations() {
|
||||
let mut vartab = VarTab::new();
|
||||
let mut vartab = VarTab::new();
|
||||
|
||||
// Clear initial args and set fresh ones
|
||||
vartab.clear_args();
|
||||
// Clear initial args and set fresh ones
|
||||
vartab.clear_args();
|
||||
|
||||
// Push args (clear_args leaves $0, so these become $1, $2, $3)
|
||||
vartab.bpush_arg("arg1".to_string());
|
||||
vartab.bpush_arg("arg2".to_string());
|
||||
vartab.bpush_arg("arg3".to_string());
|
||||
// Push args (clear_args leaves $0, so these become $1, $2, $3)
|
||||
vartab.bpush_arg("arg1".to_string());
|
||||
vartab.bpush_arg("arg2".to_string());
|
||||
vartab.bpush_arg("arg3".to_string());
|
||||
|
||||
// Get initial arg count
|
||||
let initial_len = vartab.sh_argv().len();
|
||||
// Get initial arg count
|
||||
let initial_len = vartab.sh_argv().len();
|
||||
|
||||
// Pop first arg (removes $0)
|
||||
let popped = vartab.fpop_arg();
|
||||
assert!(popped.is_some());
|
||||
// Pop first arg (removes $0)
|
||||
let popped = vartab.fpop_arg();
|
||||
assert!(popped.is_some());
|
||||
|
||||
// Should have one fewer arg
|
||||
assert_eq!(vartab.sh_argv().len(), initial_len - 1);
|
||||
// Should have one fewer arg
|
||||
assert_eq!(vartab.sh_argv().len(), initial_len - 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -463,39 +485,39 @@ fn vartab_shell_argv_operations() {
|
||||
|
||||
#[test]
|
||||
fn varflags_none() {
|
||||
let flags = VarFlags::NONE;
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
assert!(!flags.contains(VarFlags::LOCAL));
|
||||
assert!(!flags.contains(VarFlags::READONLY));
|
||||
let flags = VarFlags::NONE;
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
assert!(!flags.contains(VarFlags::LOCAL));
|
||||
assert!(!flags.contains(VarFlags::READONLY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varflags_export() {
|
||||
let flags = VarFlags::EXPORT;
|
||||
assert!(flags.contains(VarFlags::EXPORT));
|
||||
assert!(!flags.contains(VarFlags::LOCAL));
|
||||
let flags = VarFlags::EXPORT;
|
||||
assert!(flags.contains(VarFlags::EXPORT));
|
||||
assert!(!flags.contains(VarFlags::LOCAL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varflags_local() {
|
||||
let flags = VarFlags::LOCAL;
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
assert!(flags.contains(VarFlags::LOCAL));
|
||||
let flags = VarFlags::LOCAL;
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
assert!(flags.contains(VarFlags::LOCAL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varflags_combine() {
|
||||
let flags = VarFlags::EXPORT | VarFlags::LOCAL;
|
||||
assert!(flags.contains(VarFlags::EXPORT));
|
||||
assert!(flags.contains(VarFlags::LOCAL));
|
||||
assert!(!flags.contains(VarFlags::READONLY));
|
||||
let flags = VarFlags::EXPORT | VarFlags::LOCAL;
|
||||
assert!(flags.contains(VarFlags::EXPORT));
|
||||
assert!(flags.contains(VarFlags::LOCAL));
|
||||
assert!(!flags.contains(VarFlags::READONLY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn varflags_readonly() {
|
||||
let flags = VarFlags::READONLY;
|
||||
assert!(flags.contains(VarFlags::READONLY));
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
let flags = VarFlags::READONLY;
|
||||
assert!(flags.contains(VarFlags::READONLY));
|
||||
assert!(!flags.contains(VarFlags::EXPORT));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -504,49 +526,70 @@ fn varflags_readonly() {
|
||||
|
||||
#[test]
|
||||
fn shellparam_is_global() {
|
||||
assert!(ShellParam::Status.is_global());
|
||||
assert!(ShellParam::ShPid.is_global());
|
||||
assert!(ShellParam::LastJob.is_global());
|
||||
assert!(ShellParam::ShellName.is_global());
|
||||
assert!(ShellParam::Status.is_global());
|
||||
assert!(ShellParam::ShPid.is_global());
|
||||
assert!(ShellParam::LastJob.is_global());
|
||||
assert!(ShellParam::ShellName.is_global());
|
||||
|
||||
assert!(!ShellParam::Pos(1).is_global());
|
||||
assert!(!ShellParam::AllArgs.is_global());
|
||||
assert!(!ShellParam::AllArgsStr.is_global());
|
||||
assert!(!ShellParam::ArgCount.is_global());
|
||||
assert!(!ShellParam::Pos(1).is_global());
|
||||
assert!(!ShellParam::AllArgs.is_global());
|
||||
assert!(!ShellParam::AllArgsStr.is_global());
|
||||
assert!(!ShellParam::ArgCount.is_global());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shellparam_from_str() {
|
||||
assert!(matches!("?".parse::<ShellParam>().unwrap(), ShellParam::Status));
|
||||
assert!(matches!("$".parse::<ShellParam>().unwrap(), ShellParam::ShPid));
|
||||
assert!(matches!("!".parse::<ShellParam>().unwrap(), ShellParam::LastJob));
|
||||
assert!(matches!("0".parse::<ShellParam>().unwrap(), ShellParam::ShellName));
|
||||
assert!(matches!("@".parse::<ShellParam>().unwrap(), ShellParam::AllArgs));
|
||||
assert!(matches!("*".parse::<ShellParam>().unwrap(), ShellParam::AllArgsStr));
|
||||
assert!(matches!("#".parse::<ShellParam>().unwrap(), ShellParam::ArgCount));
|
||||
assert!(matches!(
|
||||
"?".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::Status
|
||||
));
|
||||
assert!(matches!(
|
||||
"$".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::ShPid
|
||||
));
|
||||
assert!(matches!(
|
||||
"!".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::LastJob
|
||||
));
|
||||
assert!(matches!(
|
||||
"0".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::ShellName
|
||||
));
|
||||
assert!(matches!(
|
||||
"@".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::AllArgs
|
||||
));
|
||||
assert!(matches!(
|
||||
"*".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::AllArgsStr
|
||||
));
|
||||
assert!(matches!(
|
||||
"#".parse::<ShellParam>().unwrap(),
|
||||
ShellParam::ArgCount
|
||||
));
|
||||
|
||||
match "1".parse::<ShellParam>().unwrap() {
|
||||
ShellParam::Pos(n) => assert_eq!(n, 1),
|
||||
_ => panic!("Expected Pos(1)"),
|
||||
}
|
||||
match "1".parse::<ShellParam>().unwrap() {
|
||||
ShellParam::Pos(n) => assert_eq!(n, 1),
|
||||
_ => panic!("Expected Pos(1)"),
|
||||
}
|
||||
|
||||
match "42".parse::<ShellParam>().unwrap() {
|
||||
ShellParam::Pos(n) => assert_eq!(n, 42),
|
||||
_ => panic!("Expected Pos(42)"),
|
||||
}
|
||||
match "42".parse::<ShellParam>().unwrap() {
|
||||
ShellParam::Pos(n) => assert_eq!(n, 42),
|
||||
_ => panic!("Expected Pos(42)"),
|
||||
}
|
||||
|
||||
assert!("invalid".parse::<ShellParam>().is_err());
|
||||
assert!("invalid".parse::<ShellParam>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shellparam_display() {
|
||||
assert_eq!(ShellParam::Status.to_string(), "?");
|
||||
assert_eq!(ShellParam::ShPid.to_string(), "$");
|
||||
assert_eq!(ShellParam::LastJob.to_string(), "!");
|
||||
assert_eq!(ShellParam::ShellName.to_string(), "0");
|
||||
assert_eq!(ShellParam::AllArgs.to_string(), "@");
|
||||
assert_eq!(ShellParam::AllArgsStr.to_string(), "*");
|
||||
assert_eq!(ShellParam::ArgCount.to_string(), "#");
|
||||
assert_eq!(ShellParam::Pos(1).to_string(), "1");
|
||||
assert_eq!(ShellParam::Pos(99).to_string(), "99");
|
||||
assert_eq!(ShellParam::Status.to_string(), "?");
|
||||
assert_eq!(ShellParam::ShPid.to_string(), "$");
|
||||
assert_eq!(ShellParam::LastJob.to_string(), "!");
|
||||
assert_eq!(ShellParam::ShellName.to_string(), "0");
|
||||
assert_eq!(ShellParam::AllArgs.to_string(), "@");
|
||||
assert_eq!(ShellParam::AllArgsStr.to_string(), "*");
|
||||
assert_eq!(ShellParam::ArgCount.to_string(), "#");
|
||||
assert_eq!(ShellParam::Pos(1).to_string(), "1");
|
||||
assert_eq!(ShellParam::Pos(99).to_string(), "99");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user