about summary refs log tree commit diff
path: root/pkgs/profpatsch/execline
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2020-06-22 00:10:59 +0200
committerProfpatsch <mail@profpatsch.de>2020-06-22 00:10:59 +0200
commitf77c71721f8786f64684d6c543c7962ffa0e3c16 (patch)
tree832efdea1d83adaf43ee648c2f6d6e6eafecd945 /pkgs/profpatsch/execline
parent669e6cfa72d733b9153ba254f429b1262f326848 (diff)
pkgs/profpatsch/execline: el-semicolon block parsing
Diffstat (limited to 'pkgs/profpatsch/execline')
-rw-r--r--pkgs/profpatsch/execline/el-semicolon.nix19
-rw-r--r--pkgs/profpatsch/execline/el_semicolon.rs131
2 files changed, 150 insertions, 0 deletions
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<Vec<Arg<'a>>, Error> {
+    let mut cur_block : Option<Vec<&'a [u8]>> = None;
+    let mut res : Vec<Arg<'a>> = 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)
+        )
+    }
+}