Implemented an abstraction for extracting flags from builtins
This commit is contained in:
@@ -1,25 +1,70 @@
|
|||||||
use crate::{builtin::setup_builtin, jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{builtin::setup_builtin, getopt::{get_opts_from_tokens, Opt, ECHO_OPTS}, jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct EchoFlags: u32 {
|
||||||
|
const NO_NEWLINE = 0b000001;
|
||||||
|
const USE_STDERR = 0b000010;
|
||||||
|
const USE_ESCAPE = 0b000100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
|
let (argv,opts) = get_opts_from_tokens(argv);
|
||||||
|
let flags = get_echo_flags(opts).blame(blame)?;
|
||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||||
|
borrow_fd(STDERR_FILENO)
|
||||||
|
} else {
|
||||||
|
borrow_fd(STDOUT_FILENO)
|
||||||
|
};
|
||||||
|
|
||||||
let mut echo_output = argv.into_iter()
|
let mut echo_output = argv.into_iter()
|
||||||
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
echo_output.push('\n');
|
if !flags.contains(EchoFlags::NO_NEWLINE) {
|
||||||
|
echo_output.push('\n')
|
||||||
|
}
|
||||||
|
|
||||||
write(stdout, echo_output.as_bytes())?;
|
write(output_channel, echo_output.as_bytes())?;
|
||||||
|
|
||||||
io_frame.unwrap().restore()?;
|
io_frame.unwrap().restore()?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_echo_flags(mut opts: Vec<Opt>) -> ShResult<EchoFlags> {
|
||||||
|
let mut flags = EchoFlags::empty();
|
||||||
|
|
||||||
|
while let Some(opt) = opts.pop() {
|
||||||
|
if !ECHO_OPTS.contains(&opt) {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("echo: Unexpected flag '{opt}'"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let Opt::Short(opt) = opt else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
'n' => flags |= EchoFlags::NO_NEWLINE,
|
||||||
|
'r' => flags |= EchoFlags::USE_STDERR,
|
||||||
|
'e' => flags |= EchoFlags::USE_ESCAPE,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(flags)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ pub mod jobs;
|
|||||||
pub mod signal;
|
pub mod signal;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
pub mod getopt;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use expand::expand_aliases;
|
use expand::expand_aliases;
|
||||||
|
use getopt::get_opts;
|
||||||
use libsh::error::ShResult;
|
use libsh::error::ShResult;
|
||||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream, Tk}, Ast, ParseStream, ParsedSrc};
|
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream, Tk}, Ast, ParseStream, ParsedSrc};
|
||||||
use procio::IoFrame;
|
use procio::IoFrame;
|
||||||
|
|||||||
89
src/getopt.rs
Normal file
89
src/getopt.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::{ops::Deref, str::FromStr, sync::{Arc, LazyLock}};
|
||||||
|
|
||||||
|
use fmt::Display;
|
||||||
|
|
||||||
|
use crate::{libsh::error::ShResult, parse::lex::Tk, prelude::*};
|
||||||
|
|
||||||
|
type OptSet = Arc<[Opt]>;
|
||||||
|
|
||||||
|
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {[
|
||||||
|
Opt::Short('n'),
|
||||||
|
Opt::Short('E'),
|
||||||
|
Opt::Short('e'),
|
||||||
|
Opt::Short('r'),
|
||||||
|
].into()});
|
||||||
|
|
||||||
|
#[derive(Clone,PartialEq,Eq,Debug)]
|
||||||
|
pub enum Opt {
|
||||||
|
Long(String),
|
||||||
|
Short(char)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opt {
|
||||||
|
pub fn parse(s: &str) -> Vec<Self> {
|
||||||
|
flog!(DEBUG, s);
|
||||||
|
let mut opts = vec![];
|
||||||
|
|
||||||
|
if s.starts_with("--") {
|
||||||
|
opts.push(Opt::Long(s.trim_start_matches('-').to_string()))
|
||||||
|
} else if s.starts_with('-') {
|
||||||
|
let mut chars = s.trim_start_matches('-').chars();
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
opts.push(Self::Short(ch))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flog!(DEBUG,opts);
|
||||||
|
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Opt {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Long(opt) => write!(f,"--{}",opt),
|
||||||
|
Self::Short(opt) => write!(f,"-{}",opt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_opts(words: Vec<String>) -> (Vec<String>,Vec<Opt>) {
|
||||||
|
let mut words_iter = words.into_iter();
|
||||||
|
let mut opts = vec![];
|
||||||
|
let mut non_opts = vec![];
|
||||||
|
|
||||||
|
while let Some(word) = words_iter.next() {
|
||||||
|
flog!(DEBUG, opts,non_opts);
|
||||||
|
if &word == "--" {
|
||||||
|
non_opts.extend(words_iter);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let parsed_opts = Opt::parse(&word);
|
||||||
|
if parsed_opts.is_empty() {
|
||||||
|
non_opts.push(word)
|
||||||
|
} else {
|
||||||
|
opts.extend(parsed_opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(non_opts,opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_opts_from_tokens(tokens: Vec<Tk>) -> (Vec<Tk>, Vec<Opt>) {
|
||||||
|
let mut tokens_iter = tokens.into_iter();
|
||||||
|
let mut opts = vec![];
|
||||||
|
let mut non_opts = vec![];
|
||||||
|
|
||||||
|
while let Some(token) = tokens_iter.next() {
|
||||||
|
if &token.to_string() == "--" {
|
||||||
|
non_opts.extend(tokens_iter);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let parsed_opts = Opt::parse(&token.to_string());
|
||||||
|
if parsed_opts.is_empty() {
|
||||||
|
non_opts.push(token)
|
||||||
|
} else {
|
||||||
|
opts.extend(parsed_opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(non_opts,opts)
|
||||||
|
}
|
||||||
@@ -461,7 +461,6 @@ impl Job {
|
|||||||
if child.pid == Pid::this() {
|
if child.pid == Pid::this() {
|
||||||
// TODO: figure out some way to get the exit code of builtins
|
// TODO: figure out some way to get the exit code of builtins
|
||||||
let code = state::get_status();
|
let code = state::get_status();
|
||||||
flog!(DEBUG,code);
|
|
||||||
stats.push(WtStat::Exited(child.pid, code));
|
stats.push(WtStat::Exited(child.pid, code));
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -621,9 +620,7 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
disable_reaping()?;
|
disable_reaping()?;
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
flog!(DEBUG,statuses);
|
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
flog!(DEBUG,status);
|
|
||||||
match status {
|
match status {
|
||||||
WtStat::Exited(_, exit_code) => {
|
WtStat::Exited(_, exit_code) => {
|
||||||
code = exit_code;
|
code = exit_code;
|
||||||
|
|||||||
@@ -412,7 +412,6 @@ impl LexStream {
|
|||||||
subsh_tk.flags |= TkFlags::IS_SUBSH;
|
subsh_tk.flags |= TkFlags::IS_SUBSH;
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
flog!(DEBUG, subsh_tk);
|
|
||||||
return Ok(subsh_tk)
|
return Ok(subsh_tk)
|
||||||
}
|
}
|
||||||
'{' if pos == self.cursor && self.next_is_cmd() => {
|
'{' if pos == self.cursor && self.next_is_cmd() => {
|
||||||
@@ -464,7 +463,6 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
flog!(DEBUG,new_tk);
|
|
||||||
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
return Err(
|
return Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
@@ -509,7 +507,6 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
flog!(DEBUG, self.slice_from_cursor());
|
|
||||||
Ok(new_tk)
|
Ok(new_tk)
|
||||||
}
|
}
|
||||||
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ impl ParsedSrc {
|
|||||||
for token in LexStream::new(self.src.clone(), LexFlags::empty()) {
|
for token in LexStream::new(self.src.clone(), LexFlags::empty()) {
|
||||||
tokens.push(token?);
|
tokens.push(token?);
|
||||||
}
|
}
|
||||||
flog!(DEBUG,tokens);
|
|
||||||
|
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for result in ParseStream::new(tokens) {
|
for result in ParseStream::new(tokens) {
|
||||||
@@ -1130,3 +1129,91 @@ fn is_func_name(tk: Option<&Tk>) -> bool {
|
|||||||
(tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()"))
|
(tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform an operation on the child nodes of a given node
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// node: A mutable reference to a node to be operated on
|
||||||
|
/// filter: A closure or function which checks an attribute of a child node and returns a boolean
|
||||||
|
/// operation: The closure or function to apply to a child node which matches on the filter
|
||||||
|
///
|
||||||
|
/// Very useful for testing, i.e. needing to extract specific types of nodes from the AST to inspect values
|
||||||
|
pub fn node_operation<F1,F2>(node: &mut Node, filter: &F1, operation: &mut F2)
|
||||||
|
where
|
||||||
|
F1: Fn(&Node) -> bool,
|
||||||
|
F2: FnMut(&mut Node)
|
||||||
|
{
|
||||||
|
let check_node = |node: &mut Node, filter: &F1, operation: &mut F2| {
|
||||||
|
if filter(&node) {
|
||||||
|
operation(node);
|
||||||
|
} else {
|
||||||
|
node_operation::<F1,F2>(node, filter, operation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if filter(node) {
|
||||||
|
operation(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
match node.class {
|
||||||
|
NdRule::IfNode { ref mut cond_nodes, ref mut else_block } => {
|
||||||
|
for node in cond_nodes {
|
||||||
|
let CondNode { cond, body } = node;
|
||||||
|
check_node(cond,filter,operation);
|
||||||
|
for body_node in body {
|
||||||
|
check_node(body_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for else_node in else_block {
|
||||||
|
check_node(else_node,filter,operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
NdRule::LoopNode { kind: _, ref mut cond_node } => {
|
||||||
|
let CondNode { cond, body } = cond_node;
|
||||||
|
check_node(cond,filter,operation);
|
||||||
|
for body_node in body {
|
||||||
|
check_node(body_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::ForNode { vars: _, arr: _, ref mut body } => {
|
||||||
|
for body_node in body {
|
||||||
|
check_node(body_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::CaseNode { pattern: _, ref mut case_blocks } => {
|
||||||
|
for block in case_blocks {
|
||||||
|
let CaseNode { pattern: _, body } = block;
|
||||||
|
for body_node in body {
|
||||||
|
check_node(body_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Command { ref mut assignments, argv: _ } => {
|
||||||
|
for assign_node in assignments {
|
||||||
|
check_node(assign_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Pipeline { ref mut cmds, pipe_err: _ } => {
|
||||||
|
for cmd_node in cmds {
|
||||||
|
check_node(cmd_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Conjunction { ref mut elements } => {
|
||||||
|
for node in elements.iter_mut() {
|
||||||
|
let ConjunctNode { cmd, operator: _ } = node;
|
||||||
|
check_node(cmd,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Assignment { kind: _, var: _, val: _ } => return, // No nodes to check
|
||||||
|
NdRule::BraceGrp { ref mut body } => {
|
||||||
|
for body_node in body {
|
||||||
|
check_node(body_node,filter,operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::FuncDef { name: _, ref mut body } => {
|
||||||
|
check_node(body,filter,operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -317,8 +317,6 @@ pub fn get_status() -> i32 {
|
|||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn set_status(code: i32) {
|
pub fn set_status(code: i32) {
|
||||||
flog!(DEBUG,std::panic::Location::caller());
|
|
||||||
flog!(DEBUG,code);
|
|
||||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fn simple_expansion() {
|
|||||||
let var_tk = tokens.pop().unwrap();
|
let var_tk = tokens.pop().unwrap();
|
||||||
|
|
||||||
let var_span = var_tk.span.clone();
|
let var_span = var_tk.span.clone();
|
||||||
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
|
let exp_tk = var_tk.expand(var_span, TkFlags::empty()).unwrap();
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,16 @@ fn expand_multiple_aliases() {
|
|||||||
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz")
|
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_in_arg_position() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
|
|
||||||
|
let input = String::from("echo foo");
|
||||||
|
|
||||||
|
let result = expand_aliases(input.clone(), HashSet::new());
|
||||||
|
assert_eq!(input,result)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_recursive_alias() {
|
fn expand_recursive_alias() {
|
||||||
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
@@ -74,9 +84,9 @@ fn expand_recursive_alias() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_infinite_recursive_alias() {
|
fn test_infinite_recursive_alias() {
|
||||||
write_logic(|l| l.insert_alias("foo", "foo"));
|
write_logic(|l| l.insert_alias("foo", "foo bar"));
|
||||||
|
|
||||||
let input = String::from("foo");
|
let input = String::from("foo");
|
||||||
let result = expand_aliases(input, HashSet::new());
|
let result = expand_aliases(input, HashSet::new());
|
||||||
assert_eq!(result.as_str(),"foo")
|
assert_eq!(result.as_str(),"foo bar")
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/tests/getopt.rs
Normal file
37
src/tests/getopt.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use getopt::get_opts_from_tokens;
|
||||||
|
use parse::NdRule;
|
||||||
|
use tests::get_nodes;
|
||||||
|
|
||||||
|
use super::super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn getopt_from_argv() {
|
||||||
|
let node = get_nodes("echo -n -e foo", |node| matches!(node.class, NdRule::Command {..}))
|
||||||
|
.pop()
|
||||||
|
.unwrap();
|
||||||
|
let NdRule::Command { assignments, argv } = node.class else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (words,opts) = get_opts_from_tokens(argv);
|
||||||
|
insta::assert_debug_snapshot!(words);
|
||||||
|
insta::assert_debug_snapshot!(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn getopt_simple() {
|
||||||
|
let raw = "echo -n foo".split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (words,opts) = get_opts(raw);
|
||||||
|
insta::assert_debug_snapshot!(words);
|
||||||
|
insta::assert_debug_snapshot!(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn getopt_multiple_short() {
|
||||||
|
let raw = "echo -nre foo".split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (words,opts) = get_opts(raw);
|
||||||
|
insta::assert_debug_snapshot!(words);
|
||||||
|
insta::assert_debug_snapshot!(opts);
|
||||||
|
}
|
||||||
@@ -1,5 +1,32 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::parse::{lex::{LexFlags, LexStream}, node_operation, Node, ParseStream};
|
||||||
|
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
pub mod term;
|
pub mod term;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod getopt;
|
||||||
|
|
||||||
|
/// Unsafe to use outside of tests
|
||||||
|
pub fn get_nodes<F1>(input: &str, filter: F1) -> Vec<Node>
|
||||||
|
where
|
||||||
|
F1: Fn(&Node) -> bool
|
||||||
|
{
|
||||||
|
let mut nodes = vec![];
|
||||||
|
let tokens = LexStream::new(Rc::new(input.into()), LexFlags::empty())
|
||||||
|
.map(|tk| tk.unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut parsed_nodes = ParseStream::new(tokens)
|
||||||
|
.map(|nd| nd.unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for node in parsed_nodes.iter_mut() {
|
||||||
|
node_operation(node,
|
||||||
|
&filter,
|
||||||
|
&mut |node: &mut Node| nodes.push(node.clone())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use parse::{node_operation, NdRule, Node};
|
||||||
|
|
||||||
use super::super::*;
|
use super::super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -196,3 +198,22 @@ fn parse_cursed() {
|
|||||||
// 15,000 line snapshot file btw
|
// 15,000 line snapshot file btw
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_node_operation() {
|
||||||
|
let input = String::from("echo hello world; echo foo bar");
|
||||||
|
let mut check_nodes = vec![];
|
||||||
|
let mut tokens: Vec<Tk> = LexStream::new(input.into(), LexFlags::empty())
|
||||||
|
.map(|tk| tk.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let nodes = ParseStream::new(tokens)
|
||||||
|
.map(|nd| nd.unwrap());
|
||||||
|
|
||||||
|
for mut node in nodes {
|
||||||
|
node_operation(&mut node,
|
||||||
|
&|node: &Node| matches!(node.class, NdRule::Command {..}),
|
||||||
|
&mut |node: &mut Node| check_nodes.push(node.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
insta::assert_debug_snapshot!(check_nodes)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: opts
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Short(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
Short(
|
||||||
|
'e',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: words
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 0..4,
|
||||||
|
source: "echo -n -e foo",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 11..14,
|
||||||
|
source: "echo -n -e foo",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: opts
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Short(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
Short(
|
||||||
|
'r',
|
||||||
|
),
|
||||||
|
Short(
|
||||||
|
'e',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: words
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"echo",
|
||||||
|
"foo",
|
||||||
|
]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: opts
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Short(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/getopt.rs
|
||||||
|
expression: words
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"echo",
|
||||||
|
"foo",
|
||||||
|
]
|
||||||
162
src/tests/snapshots/fern__tests__parser__node_operation.snap
Normal file
162
src/tests/snapshots/fern__tests__parser__node_operation.snap
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/parser.rs
|
||||||
|
expression: check_nodes
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Node {
|
||||||
|
class: Command {
|
||||||
|
assignments: [],
|
||||||
|
argv: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 0..4,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 5..10,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 11..16,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 0..4,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 5..10,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 11..16,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 16..18,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Node {
|
||||||
|
class: Command {
|
||||||
|
assignments: [],
|
||||||
|
argv: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..22,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 23..26,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 27..30,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..22,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 23..26,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 27..30,
|
||||||
|
source: "echo hello world; echo foo bar",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user