From f77c71721f8786f64684d6c543c7962ffa0e3c16 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Mon, 22 Jun 2020 00:10:59 +0200 Subject: pkgs/profpatsch/execline: el-semicolon block parsing --- pkgs/profpatsch/execline/el-semicolon.nix | 19 +++++ pkgs/profpatsch/execline/el_semicolon.rs | 131 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 pkgs/profpatsch/execline/el-semicolon.nix create mode 100644 pkgs/profpatsch/execline/el_semicolon.rs (limited to 'pkgs/profpatsch/execline') diff --git a/pkgs/profpatsch/execline/el-semicolon.nix b/pkgs/profpatsch/execline/el-semicolon.nix new file mode 100644 index 00000000..73694bdb --- /dev/null +++ b/pkgs/profpatsch/execline/el-semicolon.nix @@ -0,0 +1,19 @@ +{ writeRustSimpleLib }: + +let + el-semicolon-common = tests: writeRustSimpleLib "el_semicolon" { + buildTests = tests; + release = false; + verbose = true; + } ./el_semicolon.rs; + + el-semicolon-tests = el-semicolon-common true; + + el-semicolon = el-semicolon-common false; + +in { + inherit + el-semicolon + el-semicolon-tests + ; +} diff --git a/pkgs/profpatsch/execline/el_semicolon.rs b/pkgs/profpatsch/execline/el_semicolon.rs new file mode 100644 index 00000000..9dc08d9b --- /dev/null +++ b/pkgs/profpatsch/execline/el_semicolon.rs @@ -0,0 +1,131 @@ + +/// Every element inside the block starts with a space +const BLOCK_QUOTE_CHAR : u8 = b' '; +/// The end of a block is signified by an empty string +const BLOCK_END : &'static [u8] = &[]; + +/// A parsed execline argument. +#[derive(Debug, PartialEq, Eq)] +enum Arg<'a> { + /// Normal argument. + Arg(&'a [u8]), + /// A block. + /// + /// On the command line a block is represented + /// by a list of arguments which start with a space + /// and end with an empty string. + Block(Vec<&'a [u8]>) +} + +#[derive(Debug, PartialEq, Eq)] +enum Error { + /// The argument was not quoted, at index. + UnquotedArgument(usize), + /// The last block was not terminated + UnterminatedBlock +} + +/// Parse a command line into a list of `Arg`s. +/// +/// Blocks can be nested by adding more spaces, +/// but `el_semicolon` will only parse one level. +/// Usually that is intended, because nested blocks +/// are intended to be parsed by nested programs. +fn el_semicolon<'a>(args: &'a [&'a [u8]]) -> Result>, Error> { + let mut cur_block : Option> = None; + let mut res : Vec> = vec![]; + for (i, arg) in args.iter().enumerate() { + if arg == &BLOCK_END { + let bl = cur_block.take(); + match bl { + None => res.push(Arg::Arg(arg)), + Some(bl) => res.push(Arg::Block(bl)) + } + } else { + match arg[0] { + BLOCK_QUOTE_CHAR => { + let new = &arg[1..]; + cur_block = Some(cur_block.map_or_else( + || vec![new], + |mut bl| { bl.push(new); bl } + )) + }, + _ => { + if cur_block != None { + return Err(Error::UnquotedArgument(i)); + } + res.push(Arg::Arg(arg)) + } + } + } + } + if cur_block != None { + Err(Error::UnterminatedBlock) + } else { + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn success() { + assert_eq!( + el_semicolon(&vec![ + "-b".as_bytes(), + " echo".as_bytes(), + " hi".as_bytes(), + "".as_bytes(), + "test".as_bytes(), + "".as_bytes(), + ]), + Ok(vec![ + Arg::Arg("-b".as_bytes()), + Arg::Block(vec![ + "echo".as_bytes(), + "hi".as_bytes(), + ]), + Arg::Arg("test".as_bytes()), + Arg::Arg("".as_bytes()), + ]) + ) + } + + #[test] + fn unquoted_argument() { + assert_eq!( + el_semicolon(&vec![ + "-b".as_bytes(), + " echo".as_bytes(), + "hi".as_bytes(), + "".as_bytes(), + "test".as_bytes(), + "".as_bytes(), + ]), + Err(Error::UnquotedArgument(2)) + ); + assert_eq!( + el_semicolon(&vec![ + " -b".as_bytes(), + " echo".as_bytes(), + "".as_bytes(), + " test".as_bytes(), + "a".as_bytes(), + ]), + Err(Error::UnquotedArgument(4)) + ) + } + + #[test] + fn unterminated_block() { + assert_eq!( + el_semicolon(&vec![ + "-b".as_bytes(), + " echo".as_bytes(), + ]), + Err(Error::UnterminatedBlock) + ) + } +} -- cgit 1.4.1