Shellvars: add unsupported syntax to tests (Issue #343)

(* Test for shell lens *)
module Test_shellvars =

  let lns = Shellvars.lns

  let eth_static = "# Intel Corporation PRO/100 VE Network Connection
DEVICE=eth0
BOOTPROTO=static
BROADCAST=172.31.0.255
HWADDR=ab:cd:ef:12:34:56
export IPADDR=172.31.0.31 # this is our IP
#DHCP_HOSTNAME=host.example.com
NETMASK=255.255.255.0
NETWORK=172.31.0.0
unset ONBOOT    #   We do not want this var
"
  let empty_val = "EMPTY=\nDEVICE=eth0\n"

  let key_brack = "SOME_KEY[1]=\nDEVICE=eth0\n"

  test lns get eth_static =
    { "#comment" = "Intel Corporation PRO/100 VE Network Connection" }
    { "DEVICE" = "eth0" }
    { "BOOTPROTO" = "static" }
    { "BROADCAST" = "172.31.0.255" }
    { "HWADDR" = "ab:cd:ef:12:34:56" }
    { "IPADDR" = "172.31.0.31"
        { "export" }
        { "#comment" = "this is our IP" } }
    { "#comment" = "DHCP_HOSTNAME=host.example.com" }
    { "NETMASK" = "255.255.255.0" }
    { "NETWORK" = "172.31.0.0" }
    { "@unset"
        { "1" = "ONBOOT" }
        { "#comment" = "We do not want this var" } }

  test lns put eth_static after
      set "BOOTPROTO" "dhcp" ;
      rm "IPADDR" ;
      rm "BROADCAST" ;
      rm "NETMASK" ;
      rm "NETWORK"
  = "# Intel Corporation PRO/100 VE Network Connection
DEVICE=eth0
BOOTPROTO=dhcp
HWADDR=ab:cd:ef:12:34:56
#DHCP_HOSTNAME=host.example.com
unset ONBOOT    #   We do not want this var
"
  test lns get empty_val =
    { "EMPTY" = "" } { "DEVICE" = "eth0" }

  test lns get key_brack =
    { "SOME_KEY[1]" = "" } { "DEVICE" = "eth0" }

  test lns get "smartd_opts=\"-q never\"\n" =
    { "smartd_opts" = "\"-q never\"" }

  test lns get "var=val  \n" = { "var" = "val" }

  test lns get ". /etc/java/java.conf\n" =
    { ".source" = "/etc/java/java.conf" }

  (* Quoted strings and other oddities *)
  test lns get "var=\"foo 'bar'\"\n" =
    { "var" = "\"foo 'bar'\"" }

  test lns get "var='Some \"funny\" value'\n" =
    { "var" = "'Some \"funny\" value'" }

  test lns get "var=\"\\\"\"\n" =
    { "var" = "\"\\\"\"" }

  test lns get "var=\\\"\n" =
    { "var" = "\\\"" }

  test lns get "var=ab#c\n" =
    { "var" = "ab#c" }

  test lns get "var=ab #c\n" =
    { "var" = "ab"
      { "#comment" = "c" } }

  test lns get "var=ab; #c\n" =
    { "var" = "ab" }
    { "#comment" = "c" }

  test lns put "var=ab; #c\n" after
    set "/#comment" "d" =
    "var=ab; #d\n"

  test lns get "var=ab;\n" =
    { "var" = "ab" }

  test lns get "var='ab#c'\n" =
    { "var" = "'ab#c'" }

  test lns get "var=\"ab#c\"\n" =
    { "var" = "\"ab#c\"" }

  test lns get "ESSID='Joe'\"'\"'s net'\n" =
    { "ESSID" = "'Joe'\"'\"'s net'" }

  test lns get "var=`ab#c`\n" =
    { "var" = "`ab#c`" }

  test lns get "var=`grep nameserver /etc/resolv.conf | head -1`\n" =
    { "var" = "`grep nameserver /etc/resolv.conf | head -1`" }

  test lns put "var=ab #c\n"
    after rm "/var/#comment" = "var=ab\n"

  test lns put "var=ab\n"
    after set "/var/#comment" "this is a var" =
       "var=ab # this is a var\n"

  (* Handling of arrays *)
  test lns get "var=(val1 \"val\\\"2\\\"\" val3)\n" =
    { "var"
        { "1" = "val1" }
        { "2" = "\"val\\\"2\\\"\"" }
        { "3" = "val3" } }

  test lns get "var=()\n" = { "var" = "()" }

  test lns put "var=()\n" after
      set "var" "value"
  = "var=value\n"

  test lns put "var=(v1 v2)\n" after
      rm "var/*" ;
      set "var" "value"
  = "var=value\n"

  test lns put "var=(v1 v2)\n" after
    set "var/3" "v3"
  = "var=(v1 v2 v3)\n"

  test lns get "var=(v1 v2   \n    \t v3)\n" =
  { "var"
    { "1" = "v1" }
    { "2" = "v2" }
    { "3" = "v3" } }

  (* Allow spaces after/before opening/closing parens for array *)
  test lns get "config_eth1=( \"10.128.0.48/24\" )\n" =
  { "config_eth1"  { "1" = "\"10.128.0.48/24\"" } }

  (* Bug 109: allow a bare export *)
  test lns get "export FOO\n" =
  { "@export"
    { "1" = "FOO" } }

  (* Bug 73: allow ulimit builtin *)
  test lns get "ulimit -c unlimited\n" =
  { "@builtin" = "ulimit" { "args" = "-c unlimited" } }

  (* Allow shift builtin *)
  test Shellvars.lns get "shift\nshift 2\n" =
  { "@builtin" = "shift" }
  { "@builtin" = "shift" { "args" = "2" } }

  (* Allow exit builtin *)
  test Shellvars.lns get "exit\nexit 2\n" =
  { "@builtin" = "exit" }
  { "@builtin" = "exit" { "args" = "2" } }

  (* Allow wrapping builtin arguments to multiple lines *)
  test Shellvars.lns get "ulimit -c \\\nunlimited\nulimit \\\n -x 123\n" =
  { "@builtin" = "ulimit" { "args" = "-c \\\nunlimited" } }
  { "@builtin" = "ulimit" { "args" = "-x 123" } }

  (* Test semicolons *)
  test lns get "VAR1=\"this;is;a;test\"\nVAR2=this;\n" =
  { "VAR1" = "\"this;is;a;test\"" }
  { "VAR2" = "this" }

  (* Bug 230: parse conditions *)
  test lns get "if [ -f /etc/default/keyboard ]; then\n. /etc/default/keyboard\nfi\n" =
  { "@if" = "[ -f /etc/default/keyboard ]" { ".source" = "/etc/default/keyboard" } }

  (* Recursive condition *)
  test lns get "if [ -f /tmp/file1 ]; then
  if [ -f /tmp/file2 ]
  then
    . /tmp/file2
  elif [ -f /tmp/file3 ]; then
    . /tmp/file3; else; . /tmp/file4
  fi
else
  . /tmp/file3
fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { "@if" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
      { "@elif" = "[ -f /tmp/file3 ]"
        { ".source" = "/tmp/file3" } }
      { "@else"
        { ".source" = "/tmp/file4" }
      }
    }
    { "@else"
      { ".source" = "/tmp/file3" }
    }
  }

  (* Multiple elif *)
  test Shellvars.lns get "if [ -f /tmp/file1 ]; then
  . /tmp/file1
  elif [ -f /tmp/file2 ]; then
  . /tmp/file2
  elif [ -f /tmp/file3 ]; then
  . /tmp/file3
  fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { ".source" = "/tmp/file1" }
    { "@elif" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
    }
    { "@elif" = "[ -f /tmp/file3 ]"
      { ".source" = "/tmp/file3" }
    }
  }

  (* Comment or eol *)
  test lns get "VAR=value # eol-comment\n" =
  { "VAR" = "value"
    { "#comment" = "eol-comment" }
  }

  (* One-liners *)
  test lns get "if [ -f /tmp/file1 ]; then . /tmp/file1; else . /tmp/file2; fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { ".source" = "/tmp/file1" }
    { "@else"
      { ".source" = "/tmp/file2" }
    }
  }

  (* Loops *)
  test lns get "for f in /tmp/file*; do
  while [ 1 ]; do . $f; done
done\n" =
  { "@for" = "f in /tmp/file*"
    { "@while" = "[ 1 ]"
      { ".source" = "$f" }
    }
  }

  (* Case *)
  test lns get "case $f in
  /tmp/file1)
    . /tmp/file1
    ;;
  /tmp/file2)
    . /tmp/file2
    ;;
  *)
    unset f
    ;;
esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "/tmp/file1" }
      { ".source" = "/tmp/file1" } }
    { "@case_entry"
      { "@pattern" = "/tmp/file2" }
      { ".source" = "/tmp/file2" } }
    { "@case_entry"
      { "@pattern" = "*" }
      { "@unset"
        { "1" = "f" } } } }

  (* Select *)
  test lns get "select i in a b c; do . /tmp/file$i
   done\n" =
  { "@select" = "i in a b c"
    { ".source" = "/tmp/file$i" }
  }

  (* Return *)
  test lns get "return\nreturn 2\n" =
  { "@return" }
  { "@return" = "2" }

  (* Functions *)
  test Shellvars.lns get "foo() {
  . /tmp/bar
  }\n" =
  { "@function" = "foo"
    { ".source" = "/tmp/bar" }
  }

  test Shellvars.lns get "function foo () {
  . /tmp/bar
  }\n" =
  { "@function" = "foo"
    { ".source" = "/tmp/bar" }
  }

  (* Dollar assignment *)
  test Shellvars.lns get "FOO=$(bar arg)\n" =
  { "FOO" = "$(bar arg)" }

  (* Empty lines before esac *)
  test Shellvars.lns get "case $f in
  a)
    B=C
    ;;

  esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "a" }
      { "B" = "C" } }
    }

  (* Empty lines before a case_entry *)
  test Shellvars.lns get "case $f in

  a)
    B=C
    ;;

  b)
    A=D
    ;;
  esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "a" }
      { "B" = "C" } }
    { "@case_entry"
      { "@pattern" = "b" }
      { "A" = "D" } } }

  (* Comments anywhere *)
  test Shellvars.lns get "case ${INTERFACE} in
# comment before
eth0)
# comment in
OPTIONS=()
;;

# comment before 2
*)
# comment in 2
unset f
;;
# comment after
esac\n" =
  { "@case" = "${INTERFACE}"
    { "#comment" = "comment before" }
    { "@case_entry"
      { "@pattern" = "eth0" }
      { "#comment" = "comment in" }
      { "OPTIONS" = "()" } }
    { "#comment" = "comment before 2" }
    { "@case_entry"
      { "@pattern" = "*" }
      { "#comment" = "comment in 2" }
      { "@unset"
        { "1" = "f" } } }
    { "#comment" = "comment after" } }

  (* Empty case *)
  test Shellvars.lns get "case $a in
  *)
  ;;
  esac\n" =
  { "@case" = "$a"
    { "@case_entry" { "@pattern" = "*" } } }

  (* case variables can be surrounded by double quotes *)
  test Shellvars.lns get "case \"${options}\" in
*debug*)
  shift
  ;;
esac\n" =
  { "@case" = "\"${options}\""
    { "@case_entry"
      { "@pattern" = "*debug*" }
      { "@builtin" = "shift" } } }

  (* Double quoted values can have newlines *)
  test Shellvars.lns get "FOO=\"123\n456\"\n" =
  { "FOO" = "\"123\n456\"" }

  (* Single quoted values can have newlines *)
  test Shellvars.lns get "FOO='123\n456'\n" =
  { "FOO" = "'123\n456'" }

  (* bquoted values can have semi-colons *)
  test Shellvars.lns get "FOO=`bar=date;$bar`\n" =
  { "FOO" = "`bar=date;$bar`" }

  (* dollar-assigned values can have semi-colons *)
  test Shellvars.lns get "FOO=$(bar=date;$bar)\n" =
  { "FOO" = "$(bar=date;$bar)" }

  (* dollar-assigned value in bquot *)
  test Shellvars.lns get "FOO=`echo $(date)`\n" =
  { "FOO" = "`echo $(date)`" }

  (* bquot value in dollar-assigned value *)
  test Shellvars.lns get "FOO=$(echo `date`)\n" =
  { "FOO" = "$(echo `date`)" }

  (* dbquot *)
  test Shellvars.lns get "FOO=``bar``\n" =
  { "FOO" = "``bar``" }

  (* Partial quoting is allowed *)
  test Shellvars.lns get "FOO=\"$bar\"/'baz'/$(quux)$((1 + 2))\n" =
  { "FOO" = "\"$bar\"/'baz'/$(quux)$((1 + 2))" }

  (* unset can be used on wildcard variables *)
  test Shellvars.lns get "unset ${!LC_*}\n" =
  { "@unset"
    { "1" = "${!LC_*}" } }

  (* Empty comment before entries *)
  test Shellvars.lns get "# \nfoo=bar\n" =
  { "foo" = "bar" }

  (* Empty comment after entries *)
  test Shellvars.lns get "foo=bar\n# \n\n" =
  { "foo" = "bar" }

  (* Whitespace between lines *)
  test Shellvars.lns get "DEVICE=eth0\n\nBOOTPROTO=static\n" =
    { "DEVICE" = "eth0" }
    { "BOOTPROTO" = "static" }

  (* Whitespace after line *)
  test Shellvars.lns get "DEVICE=eth0\n\n" =
    { "DEVICE" = "eth0" }

  (* Fails adding variable assignment between comment and blank line *)
  let ins_after_comment = "# foo

"
  test lns put ins_after_comment after
      insa "foo" "#comment" ;
      set "foo" "yes"
  = "# foo\n\nfoo=yes\n"

  (* Make sure to support empty comments *)
  test lns get "# foo
  #
  #
  foo=bar
  #\n" =
    { "#comment" = "foo" }
    { "foo" = "bar" }

  (* Single quotes in arrays, ticket #357 *)
  test lns get "DLAGENTS=('ftp::/usr/bin/curl -fC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'
          'scp::/usr/bin/scp -C %u %o')\n" =
    { "DLAGENTS"
      { "1" = "'ftp::/usr/bin/curl -fC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'" }
      { "2" = "'scp::/usr/bin/scp -C %u %o'" } }

  (* Accept continued lines in quoted values *)
  test lns get "BLAH=\" \
test \
test2\"\n" =
  { "BLAH" = "\" \\\ntest \\\ntest2\"" }

  (* Export of multiple variables, RHBZ#1033795 *)
  test lns get "export TestVar1 TestVar2\n" =
    { "@export"
      { "1" = "TestVar1" }
      { "2" = "TestVar2" } }

  (* Support ;; on same line as a case statement entry, RHBZ#1033799 *)
  test lns get "case $ARG in
        0) TestVar=\"test0\" ;;
        1) TestVar=\"test1\" ;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "0" }
        { "TestVar" = "\"test0\"" } }
      { "@case_entry"
        { "@pattern" = "1" }
        { "TestVar" = "\"test1\"" } } }

  (* case: support ;; on the same line with multiple commands *)
  test lns get "case $ARG in
        0) Foo=0; Bar=1;;
        1)
	   Foo=2
	   Bar=3; Baz=4;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "0" }
        { "Foo" = "0" }
        { "Bar" = "1" }
      }
      { "@case_entry"
        { "@pattern" = "1" }
        { "Foo" = "2" }
        { "Bar" = "3" }
        { "Baz" = "4" }
      }
    }

(* Test: Shellvars.lns
     Support `##` bashism in conditions (GH issue #118) *)
test Shellvars.lns get "if [ \"${APACHE_CONFDIR##/etc/apache2-}\" != \"${APACHE_CONFDIR}\" ] ; then
    SUFFIX=\"-${APACHE_CONFDIR##/etc/apache2-}\"
else
    SUFFIX=
fi\n" =
  { "@if" = "[ \"${APACHE_CONFDIR##/etc/apache2-}\" != \"${APACHE_CONFDIR}\" ]"
    { "SUFFIX" = "\"-${APACHE_CONFDIR##/etc/apache2-}\"" }
    { "@else"
      { "SUFFIX" = "" }
    }
  }

  (* Support $(( .. )) arithmetic expansion in variable assignment, RHBZ#1100550 *)
  test lns get "export MALLOC_PERTURB_=$(($RANDOM % 255 + 1))\n" =
    { "MALLOC_PERTURB_" = "$(($RANDOM % 255 + 1))"
      { "export" } }

  (*
   * Github issue 202
   *)
  let starts_with_blank = "\n  \nVAR=value\n"

  test lns get starts_with_blank = { "VAR" = "value" }

  (* It is now possible to insert at the beginning of a file
   * that starts with blank lines *)
  test lns put starts_with_blank after
    insb "#comment" "/*[1]";
    set "/#comment[1]" "a comment" =
    " # a comment\nVAR=value\n"

  (* Modifications of the file lose the blank lines though *)
  test lns put starts_with_blank after
    set "/VAR2" "abc" = "VAR=value\nVAR2=abc\n"

  test lns put starts_with_blank after
    rm "/VAR";
    set "/VAR2" "abc" = "VAR2=abc\n"

  test lns put starts_with_blank after
    rm "/VAR"         = ""

  (* Support associative arrays *)
  test lns get "var[alpha_beta,gamma]=something\n" =
    { "var[alpha_beta,gamma]" = "something" }

  (* GH #188: support more conditions *)
  test Shellvars.lns get "[ -f $FILENAME ]\n" =
    { "@condition" = "-f $FILENAME"
      { "type" = "[" } }

  test Shellvars.lns get "[[ -f $FILENAME ]]\n" =
    { "@condition" = "-f $FILENAME"
      { "type" = "[[" } }

  (* Allow wrapping loop condition to multiple lines *)
  test Shellvars.lns get "for x in foo \\\nbar\\\nbaz; do y=$x; done\n" =
  { "@for" = "x in foo \\\nbar\\\nbaz" { "y" = "$x" } }

  (* Allow quotes in loop conditions *)
  test Shellvars.lns get "for x in \"$@\"; do y=$x; done\n" =
  { "@for" = "x in \"$@\"" { "y" = "$x" } }

  (* case: support quotes and spaces in pattern lists *)
  test lns get "case $ARG in
        \"foo bar\")
           Foo=0
           ;;
        baz | quux)
           Foo=1
           ;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "\"foo bar\"" }
        { "Foo" = "0" }
      }
      { "@case_entry"
        { "@pattern" = "baz" }
        { "@pattern" = "quux" }
        { "Foo" = "1" }
      }
    }

  (* eval *)
  test lns get "eval `dircolors`\n" =
    { "@eval" = "`dircolors`" }

  (* alias *)
  test lns get "alias ls='ls $LS_OPTIONS'\n" =
    { "@alias" = "ls" { "value" = "'ls $LS_OPTIONS'" } }

  (* Allow && and || constructs after condition *)
  test Shellvars.lns get "[ -f $FILENAME ] && do this || or that\n" =
  { "@condition" = "-f $FILENAME"
    { "type" = "[" }
    { "@and" = "do this" }
    { "@or" = "or that" } }

(* Test: Shellvars.lns
     Parse (almost) any command *)
test Shellvars.lns get "echo foobar 'and this is baz'
/usr/local/bin/myscript.sh with args
echo foo \
bar\n" =
  { "@command" = "echo"
    { "@arg" = "foobar 'and this is baz'" }
  }
  { "@command" = "/usr/local/bin/myscript.sh"
    { "@arg" = "with args" }
  }
  { "@command" = "echo"
    { "@arg" = "foo \\\nbar" }
  }

(* Test: Shellvars.lns
     Support pipes in commands *)
test Shellvars.lns get "echo \"$STRING\" | grep foo\n" =
  { "@command" = "echo"
    { "@arg" = "\"$STRING\"" }
    { "@pipe"
      { "@command" = "grep"
        { "@arg" = "foo" } } } }

(* Test: Shellvars.lns
     Support && and || after command
     GH #215 *)
test Shellvars.lns get "grep -q \"Debian\" /etc/issue && echo moo\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@and"
      { "@command" = "echo"
        { "@arg" = "moo" } } } }

test Shellvars.lns get "grep -q \"Debian\" /etc/issue || echo baa\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@or"
      { "@command" = "echo"
        { "@arg" = "baa" } } } }

test Shellvars.lns get "grep -q \"Debian\" /etc/issue && DEBIAN=1\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@and"
      { "DEBIAN" = "1" } } }

test Shellvars.lns get "cat /etc/issue | grep -q \"Debian\" && echo moo || echo baa\n" =
  { "@command" = "cat"
    { "@arg" = "/etc/issue" }
    { "@pipe"
      { "@command" = "grep"
        { "@arg" = "-q \"Debian\"" }
        { "@and"
          { "@command" = "echo"
            { "@arg" = "moo" }
            { "@or"
              { "@command" = "echo"
                { "@arg" = "baa" } } } } } } } }

(* Command-specific environment variables *)
test Shellvars.lns get "abc=def \\\n  ghi=\"jkl mno\" command arg1 arg2\n" =
  { "@command" = "command"
    { "abc" = "def" }
    { "ghi" = "\"jkl mno\"" }
    { "@arg" = "arg1 arg2" }
  }

(* Wrapped command sequences *)

test Shellvars.lns get "foo && \\\nbar baz \\\n|| qux \\\n    quux\\\ncorge  grault\n" =
  { "@command" = "foo"
    { "@and"
      { "@command" = "bar"
        { "@arg" = "baz" }
	{ "@or" { "@command" = "qux" { "@arg" = "quux\\\ncorge  grault" } } }
      }
    }
  }

(* Comment after function definition (Issue #339) *)
test Shellvars.lns get "SetDir() # hello
{
 echo
}\n" =
  { "@function" = "SetDir"
    { "#comment" = "hello" }
    { "@command" = "echo" }
  }

(* Function with new lines *)
test Shellvars.lns get "MyFunc()
{
 echo
}\n" =
  { "@function" = "MyFunc"
    { "@command" = "echo" }
  }

(* Pipe and newline without cl (Issue #339) *)
test Shellvars.lns get "echo |
tr\n" =
  { "@command" = "echo"
    { "@pipe"
      { "@command" = "tr" } } }

(* Subshell (Issue #339) *)
test Shellvars.lns get "{ echo
}\n" =
  { "@subshell"
    { "@command" = "echo" }
  }

(* One-liner function *)
test Shellvars.lns get "MyFunc() { echo; }\n" =
  { "@function" = "MyFunc"
    { "@command" = "echo" }
  }

(*********************************************************
 * Group: Unsupported syntax                             *
 *                                                       *
 * The following tests are known to be failing currently *
 *********************************************************)

(* Any piping (Issue #343) *)
test Shellvars.lns get "FOO=bar && BAR=foo
echo foo || { echo bar; }
echo FOO | myfunc() { echo bar; }\n" = *

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Test for shell lens *)
module Test_shellvars =

  let lns = Shellvars.lns

  let eth_static = "# Intel Corporation PRO/100 VE Network Connection
DEVICE=eth0
BOOTPROTO=static
BROADCAST=172.31.0.255
HWADDR=ab:cd:ef:12:34:56
export IPADDR=172.31.0.31 # this is our IP
#DHCP_HOSTNAME=host.example.com
NETMASK=255.255.255.0
NETWORK=172.31.0.0
unset ONBOOT    #   We do not want this var
"
  let empty_val = "EMPTY=\nDEVICE=eth0\n"

  let key_brack = "SOME_KEY[1]=\nDEVICE=eth0\n"

  test lns get eth_static =
    { "#comment" = "Intel Corporation PRO/100 VE Network Connection" }
    { "DEVICE" = "eth0" }
    { "BOOTPROTO" = "static" }
    { "BROADCAST" = "172.31.0.255" }
    { "HWADDR" = "ab:cd:ef:12:34:56" }
    { "IPADDR" = "172.31.0.31"
        { "export" }
        { "#comment" = "this is our IP" } }
    { "#comment" = "DHCP_HOSTNAME=host.example.com" }
    { "NETMASK" = "255.255.255.0" }
    { "NETWORK" = "172.31.0.0" }
    { "@unset"
        { "1" = "ONBOOT" }
        { "#comment" = "We do not want this var" } }

  test lns put eth_static after
      set "BOOTPROTO" "dhcp" ;
      rm "IPADDR" ;
      rm "BROADCAST" ;
      rm "NETMASK" ;
      rm "NETWORK"
  = "# Intel Corporation PRO/100 VE Network Connection
DEVICE=eth0
BOOTPROTO=dhcp
HWADDR=ab:cd:ef:12:34:56
#DHCP_HOSTNAME=host.example.com
unset ONBOOT    #   We do not want this var
"
  test lns get empty_val =
    { "EMPTY" = "" } { "DEVICE" = "eth0" }

  test lns get key_brack =
    { "SOME_KEY[1]" = "" } { "DEVICE" = "eth0" }

  test lns get "smartd_opts=\"-q never\"\n" =
    { "smartd_opts" = "\"-q never\"" }

  test lns get "var=val  \n" = { "var" = "val" }

  test lns get ". /etc/java/java.conf\n" =
    { ".source" = "/etc/java/java.conf" }

  (* Quoted strings and other oddities *)
  test lns get "var=\"foo 'bar'\"\n" =
    { "var" = "\"foo 'bar'\"" }

  test lns get "var='Some \"funny\" value'\n" =
    { "var" = "'Some \"funny\" value'" }

  test lns get "var=\"\\\"\"\n" =
    { "var" = "\"\\\"\"" }

  test lns get "var=\\\"\n" =
    { "var" = "\\\"" }

  test lns get "var=ab#c\n" =
    { "var" = "ab#c" }

  test lns get "var=ab #c\n" =
    { "var" = "ab"
      { "#comment" = "c" } }

  test lns get "var=ab; #c\n" =
    { "var" = "ab" }
    { "#comment" = "c" }

  test lns put "var=ab; #c\n" after
    set "/#comment" "d" =
    "var=ab; #d\n"

  test lns get "var=ab;\n" =
    { "var" = "ab" }

  test lns get "var='ab#c'\n" =
    { "var" = "'ab#c'" }

  test lns get "var=\"ab#c\"\n" =
    { "var" = "\"ab#c\"" }

  test lns get "ESSID='Joe'\"'\"'s net'\n" =
    { "ESSID" = "'Joe'\"'\"'s net'" }

  test lns get "var=`ab#c`\n" =
    { "var" = "`ab#c`" }

  test lns get "var=`grep nameserver /etc/resolv.conf | head -1`\n" =
    { "var" = "`grep nameserver /etc/resolv.conf | head -1`" }

  test lns put "var=ab #c\n"
    after rm "/var/#comment" = "var=ab\n"

  test lns put "var=ab\n"
    after set "/var/#comment" "this is a var" =
       "var=ab # this is a var\n"

  (* Handling of arrays *)
  test lns get "var=(val1 \"val\\\"2\\\"\" val3)\n" =
    { "var"
        { "1" = "val1" }
        { "2" = "\"val\\\"2\\\"\"" }
        { "3" = "val3" } }

  test lns get "var=()\n" = { "var" = "()" }

  test lns put "var=()\n" after
      set "var" "value"
  = "var=value\n"

  test lns put "var=(v1 v2)\n" after
      rm "var/*" ;
      set "var" "value"
  = "var=value\n"

  test lns put "var=(v1 v2)\n" after
    set "var/3" "v3"
  = "var=(v1 v2 v3)\n"

  test lns get "var=(v1 v2   \n    \t v3)\n" =
  { "var"
    { "1" = "v1" }
    { "2" = "v2" }
    { "3" = "v3" } }

  (* Allow spaces after/before opening/closing parens for array *)
  test lns get "config_eth1=( \"10.128.0.48/24\" )\n" =
  { "config_eth1"  { "1" = "\"10.128.0.48/24\"" } }

  (* Bug 109: allow a bare export *)
  test lns get "export FOO\n" =
  { "@export"
    { "1" = "FOO" } }

  (* Bug 73: allow ulimit builtin *)
  test lns get "ulimit -c unlimited\n" =
  { "@builtin" = "ulimit" { "args" = "-c unlimited" } }

  (* Allow shift builtin *)
  test Shellvars.lns get "shift\nshift 2\n" =
  { "@builtin" = "shift" }
  { "@builtin" = "shift" { "args" = "2" } }

  (* Allow exit builtin *)
  test Shellvars.lns get "exit\nexit 2\n" =
  { "@builtin" = "exit" }
  { "@builtin" = "exit" { "args" = "2" } }

  (* Allow wrapping builtin arguments to multiple lines *)
  test Shellvars.lns get "ulimit -c \\\nunlimited\nulimit \\\n -x 123\n" =
  { "@builtin" = "ulimit" { "args" = "-c \\\nunlimited" } }
  { "@builtin" = "ulimit" { "args" = "-x 123" } }

  (* Test semicolons *)
  test lns get "VAR1=\"this;is;a;test\"\nVAR2=this;\n" =
  { "VAR1" = "\"this;is;a;test\"" }
  { "VAR2" = "this" }

  (* Bug 230: parse conditions *)
  test lns get "if [ -f /etc/default/keyboard ]; then\n. /etc/default/keyboard\nfi\n" =
  { "@if" = "[ -f /etc/default/keyboard ]" { ".source" = "/etc/default/keyboard" } }

  (* Recursive condition *)
  test lns get "if [ -f /tmp/file1 ]; then
  if [ -f /tmp/file2 ]
  then
    . /tmp/file2
  elif [ -f /tmp/file3 ]; then
    . /tmp/file3; else; . /tmp/file4
  fi
else
  . /tmp/file3
fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { "@if" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
      { "@elif" = "[ -f /tmp/file3 ]"
        { ".source" = "/tmp/file3" } }
      { "@else"
        { ".source" = "/tmp/file4" }
      }
    }
    { "@else"
      { ".source" = "/tmp/file3" }
    }
  }

  (* Multiple elif *)
  test Shellvars.lns get "if [ -f /tmp/file1 ]; then
  . /tmp/file1
  elif [ -f /tmp/file2 ]; then
  . /tmp/file2
  elif [ -f /tmp/file3 ]; then
  . /tmp/file3
  fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { ".source" = "/tmp/file1" }
    { "@elif" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
    }
    { "@elif" = "[ -f /tmp/file3 ]"
      { ".source" = "/tmp/file3" }
    }
  }

  (* Comment or eol *)
  test lns get "VAR=value # eol-comment\n" =
  { "VAR" = "value"
    { "#comment" = "eol-comment" }
  }

  (* One-liners *)
  test lns get "if [ -f /tmp/file1 ]; then . /tmp/file1; else . /tmp/file2; fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { ".source" = "/tmp/file1" }
    { "@else"
      { ".source" = "/tmp/file2" }
    }
  }

  (* Loops *)
  test lns get "for f in /tmp/file*; do
  while [ 1 ]; do . $f; done
done\n" =
  { "@for" = "f in /tmp/file*"
    { "@while" = "[ 1 ]"
      { ".source" = "$f" }
    }
  }

  (* Case *)
  test lns get "case $f in
  /tmp/file1)
    . /tmp/file1
    ;;
  /tmp/file2)
    . /tmp/file2
    ;;
  *)
    unset f
    ;;
esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "/tmp/file1" }
      { ".source" = "/tmp/file1" } }
    { "@case_entry"
      { "@pattern" = "/tmp/file2" }
      { ".source" = "/tmp/file2" } }
    { "@case_entry"
      { "@pattern" = "*" }
      { "@unset"
        { "1" = "f" } } } }

  (* Select *)
  test lns get "select i in a b c; do . /tmp/file$i
   done\n" =
  { "@select" = "i in a b c"
    { ".source" = "/tmp/file$i" }
  }

  (* Return *)
  test lns get "return\nreturn 2\n" =
  { "@return" }
  { "@return" = "2" }

  (* Functions *)
  test Shellvars.lns get "foo() {
  . /tmp/bar
  }\n" =
  { "@function" = "foo"
    { ".source" = "/tmp/bar" }
  }

  test Shellvars.lns get "function foo () {
  . /tmp/bar
  }\n" =
  { "@function" = "foo"
    { ".source" = "/tmp/bar" }
  }

  (* Dollar assignment *)
  test Shellvars.lns get "FOO=$(bar arg)\n" =
  { "FOO" = "$(bar arg)" }

  (* Empty lines before esac *)
  test Shellvars.lns get "case $f in
  a)
    B=C
    ;;

  esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "a" }
      { "B" = "C" } }
    }

  (* Empty lines before a case_entry *)
  test Shellvars.lns get "case $f in

  a)
    B=C
    ;;

  b)
    A=D
    ;;
  esac\n" =
  { "@case" = "$f"
    { "@case_entry"
      { "@pattern" = "a" }
      { "B" = "C" } }
    { "@case_entry"
      { "@pattern" = "b" }
      { "A" = "D" } } }

  (* Comments anywhere *)
  test Shellvars.lns get "case ${INTERFACE} in
# comment before
eth0)
# comment in
OPTIONS=()
;;

# comment before 2
*)
# comment in 2
unset f
;;
# comment after
esac\n" =
  { "@case" = "${INTERFACE}"
    { "#comment" = "comment before" }
    { "@case_entry"
      { "@pattern" = "eth0" }
      { "#comment" = "comment in" }
      { "OPTIONS" = "()" } }
    { "#comment" = "comment before 2" }
    { "@case_entry"
      { "@pattern" = "*" }
      { "#comment" = "comment in 2" }
      { "@unset"
        { "1" = "f" } } }
    { "#comment" = "comment after" } }

  (* Empty case *)
  test Shellvars.lns get "case $a in
  *)
  ;;
  esac\n" =
  { "@case" = "$a"
    { "@case_entry" { "@pattern" = "*" } } }

  (* case variables can be surrounded by double quotes *)
  test Shellvars.lns get "case \"${options}\" in
*debug*)
  shift
  ;;
esac\n" =
  { "@case" = "\"${options}\""
    { "@case_entry"
      { "@pattern" = "*debug*" }
      { "@builtin" = "shift" } } }

  (* Double quoted values can have newlines *)
  test Shellvars.lns get "FOO=\"123\n456\"\n" =
  { "FOO" = "\"123\n456\"" }

  (* Single quoted values can have newlines *)
  test Shellvars.lns get "FOO='123\n456'\n" =
  { "FOO" = "'123\n456'" }

  (* bquoted values can have semi-colons *)
  test Shellvars.lns get "FOO=`bar=date;$bar`\n" =
  { "FOO" = "`bar=date;$bar`" }

  (* dollar-assigned values can have semi-colons *)
  test Shellvars.lns get "FOO=$(bar=date;$bar)\n" =
  { "FOO" = "$(bar=date;$bar)" }

  (* dollar-assigned value in bquot *)
  test Shellvars.lns get "FOO=`echo $(date)`\n" =
  { "FOO" = "`echo $(date)`" }

  (* bquot value in dollar-assigned value *)
  test Shellvars.lns get "FOO=$(echo `date`)\n" =
  { "FOO" = "$(echo `date`)" }

  (* dbquot *)
  test Shellvars.lns get "FOO=``bar``\n" =
  { "FOO" = "``bar``" }

  (* Partial quoting is allowed *)
  test Shellvars.lns get "FOO=\"$bar\"/'baz'/$(quux)$((1 + 2))\n" =
  { "FOO" = "\"$bar\"/'baz'/$(quux)$((1 + 2))" }

  (* unset can be used on wildcard variables *)
  test Shellvars.lns get "unset ${!LC_*}\n" =
  { "@unset"
    { "1" = "${!LC_*}" } }

  (* Empty comment before entries *)
  test Shellvars.lns get "# \nfoo=bar\n" =
  { "foo" = "bar" }

  (* Empty comment after entries *)
  test Shellvars.lns get "foo=bar\n# \n\n" =
  { "foo" = "bar" }

  (* Whitespace between lines *)
  test Shellvars.lns get "DEVICE=eth0\n\nBOOTPROTO=static\n" =
    { "DEVICE" = "eth0" }
    { "BOOTPROTO" = "static" }

  (* Whitespace after line *)
  test Shellvars.lns get "DEVICE=eth0\n\n" =
    { "DEVICE" = "eth0" }

  (* Fails adding variable assignment between comment and blank line *)
  let ins_after_comment = "# foo

"
  test lns put ins_after_comment after
      insa "foo" "#comment" ;
      set "foo" "yes"
  = "# foo\n\nfoo=yes\n"

  (* Make sure to support empty comments *)
  test lns get "# foo
  #
  #
  foo=bar
  #\n" =
    { "#comment" = "foo" }
    { "foo" = "bar" }

  (* Single quotes in arrays, ticket #357 *)
  test lns get "DLAGENTS=('ftp::/usr/bin/curl -fC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'
          'scp::/usr/bin/scp -C %u %o')\n" =
    { "DLAGENTS"
      { "1" = "'ftp::/usr/bin/curl -fC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'" }
      { "2" = "'scp::/usr/bin/scp -C %u %o'" } }

  (* Accept continued lines in quoted values *)
  test lns get "BLAH=\" \
test \
test2\"\n" =
  { "BLAH" = "\" \\\ntest \\\ntest2\"" }

  (* Export of multiple variables, RHBZ#1033795 *)
  test lns get "export TestVar1 TestVar2\n" =
    { "@export"
      { "1" = "TestVar1" }
      { "2" = "TestVar2" } }

  (* Support ;; on same line as a case statement entry, RHBZ#1033799 *)
  test lns get "case $ARG in
        0) TestVar=\"test0\" ;;
        1) TestVar=\"test1\" ;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "0" }
        { "TestVar" = "\"test0\"" } }
      { "@case_entry"
        { "@pattern" = "1" }
        { "TestVar" = "\"test1\"" } } }

  (* case: support ;; on the same line with multiple commands *)
  test lns get "case $ARG in
        0) Foo=0; Bar=1;;
        1)
	   Foo=2
	   Bar=3; Baz=4;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "0" }
        { "Foo" = "0" }
        { "Bar" = "1" }
      }
      { "@case_entry"
        { "@pattern" = "1" }
        { "Foo" = "2" }
        { "Bar" = "3" }
        { "Baz" = "4" }
      }
    }

(* Test: Shellvars.lns
     Support `##` bashism in conditions (GH issue #118) *)
test Shellvars.lns get "if [ \"${APACHE_CONFDIR##/etc/apache2-}\" != \"${APACHE_CONFDIR}\" ] ; then
    SUFFIX=\"-${APACHE_CONFDIR##/etc/apache2-}\"
else
    SUFFIX=
fi\n" =
  { "@if" = "[ \"${APACHE_CONFDIR##/etc/apache2-}\" != \"${APACHE_CONFDIR}\" ]"
    { "SUFFIX" = "\"-${APACHE_CONFDIR##/etc/apache2-}\"" }
    { "@else"
      { "SUFFIX" = "" }
    }
  }

  (* Support $(( .. )) arithmetic expansion in variable assignment, RHBZ#1100550 *)
  test lns get "export MALLOC_PERTURB_=$(($RANDOM % 255 + 1))\n" =
    { "MALLOC_PERTURB_" = "$(($RANDOM % 255 + 1))"
      { "export" } }

  (*
   * Github issue 202
   *)
  let starts_with_blank = "\n  \nVAR=value\n"

  test lns get starts_with_blank = { "VAR" = "value" }

  (* It is now possible to insert at the beginning of a file
   * that starts with blank lines *)
  test lns put starts_with_blank after
    insb "#comment" "/*[1]";
    set "/#comment[1]" "a comment" =
    " # a comment\nVAR=value\n"

  (* Modifications of the file lose the blank lines though *)
  test lns put starts_with_blank after
    set "/VAR2" "abc" = "VAR=value\nVAR2=abc\n"

  test lns put starts_with_blank after
    rm "/VAR";
    set "/VAR2" "abc" = "VAR2=abc\n"

  test lns put starts_with_blank after
    rm "/VAR"         = ""

  (* Support associative arrays *)
  test lns get "var[alpha_beta,gamma]=something\n" =
    { "var[alpha_beta,gamma]" = "something" }

  (* GH #188: support more conditions *)
  test Shellvars.lns get "[ -f $FILENAME ]\n" =
    { "@condition" = "-f $FILENAME"
      { "type" = "[" } }

  test Shellvars.lns get "[[ -f $FILENAME ]]\n" =
    { "@condition" = "-f $FILENAME"
      { "type" = "[[" } }

  (* Allow wrapping loop condition to multiple lines *)
  test Shellvars.lns get "for x in foo \\\nbar\\\nbaz; do y=$x; done\n" =
  { "@for" = "x in foo \\\nbar\\\nbaz" { "y" = "$x" } }

  (* Allow quotes in loop conditions *)
  test Shellvars.lns get "for x in \"$@\"; do y=$x; done\n" =
  { "@for" = "x in \"$@\"" { "y" = "$x" } }

  (* case: support quotes and spaces in pattern lists *)
  test lns get "case $ARG in
        \"foo bar\")
           Foo=0
           ;;
        baz | quux)
           Foo=1
           ;;
esac\n" =
    { "@case" = "$ARG"
      { "@case_entry"
        { "@pattern" = "\"foo bar\"" }
        { "Foo" = "0" }
      }
      { "@case_entry"
        { "@pattern" = "baz" }
        { "@pattern" = "quux" }
        { "Foo" = "1" }
      }
    }

  (* eval *)
  test lns get "eval `dircolors`\n" =
    { "@eval" = "`dircolors`" }

  (* alias *)
  test lns get "alias ls='ls $LS_OPTIONS'\n" =
    { "@alias" = "ls" { "value" = "'ls $LS_OPTIONS'" } }

  (* Allow && and || constructs after condition *)
  test Shellvars.lns get "[ -f $FILENAME ] && do this || or that\n" =
  { "@condition" = "-f $FILENAME"
    { "type" = "[" }
    { "@and" = "do this" }
    { "@or" = "or that" } }

(* Test: Shellvars.lns
     Parse (almost) any command *)
test Shellvars.lns get "echo foobar 'and this is baz'
/usr/local/bin/myscript.sh with args
echo foo \
bar\n" =
  { "@command" = "echo"
    { "@arg" = "foobar 'and this is baz'" }
  }
  { "@command" = "/usr/local/bin/myscript.sh"
    { "@arg" = "with args" }
  }
  { "@command" = "echo"
    { "@arg" = "foo \\\nbar" }
  }

(* Test: Shellvars.lns
     Support pipes in commands *)
test Shellvars.lns get "echo \"$STRING\" | grep foo\n" =
  { "@command" = "echo"
    { "@arg" = "\"$STRING\"" }
    { "@pipe"
      { "@command" = "grep"
        { "@arg" = "foo" } } } }

(* Test: Shellvars.lns
     Support && and || after command
     GH #215 *)
test Shellvars.lns get "grep -q \"Debian\" /etc/issue && echo moo\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@and"
      { "@command" = "echo"
        { "@arg" = "moo" } } } }

test Shellvars.lns get "grep -q \"Debian\" /etc/issue || echo baa\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@or"
      { "@command" = "echo"
        { "@arg" = "baa" } } } }

test Shellvars.lns get "grep -q \"Debian\" /etc/issue && DEBIAN=1\n" =
  { "@command" = "grep"
    { "@arg" = "-q \"Debian\" /etc/issue" }
    { "@and"
      { "DEBIAN" = "1" } } }

test Shellvars.lns get "cat /etc/issue | grep -q \"Debian\" && echo moo || echo baa\n" =
  { "@command" = "cat"
    { "@arg" = "/etc/issue" }
    { "@pipe"
      { "@command" = "grep"
        { "@arg" = "-q \"Debian\"" }
        { "@and"
          { "@command" = "echo"
            { "@arg" = "moo" }
            { "@or"
              { "@command" = "echo"
                { "@arg" = "baa" } } } } } } } }

(* Command-specific environment variables *)
test Shellvars.lns get "abc=def \\\n  ghi=\"jkl mno\" command arg1 arg2\n" =
  { "@command" = "command"
    { "abc" = "def" }
    { "ghi" = "\"jkl mno\"" }
    { "@arg" = "arg1 arg2" }
  }

(* Wrapped command sequences *)

test Shellvars.lns get "foo && \\\nbar baz \\\n|| qux \\\n    quux\\\ncorge  grault\n" =
  { "@command" = "foo"
    { "@and"
      { "@command" = "bar"
        { "@arg" = "baz" }
	{ "@or" { "@command" = "qux" { "@arg" = "quux\\\ncorge  grault" } } }
      }
    }
  }

(* Comment after function definition (Issue #339) *)
test Shellvars.lns get "SetDir() # hello
{
 echo
}\n" =
  { "@function" = "SetDir"
    { "#comment" = "hello" }
    { "@command" = "echo" }
  }

(* Function with new lines *)
test Shellvars.lns get "MyFunc()
{
 echo
}\n" =
  { "@function" = "MyFunc"
    { "@command" = "echo" }
  }

(* Pipe and newline without cl (Issue #339) *)
test Shellvars.lns get "echo |
tr\n" =
  { "@command" = "echo"
    { "@pipe"
      { "@command" = "tr" } } }

(* Subshell (Issue #339) *)
test Shellvars.lns get "{ echo
}\n" =
  { "@subshell"
    { "@command" = "echo" }
  }

(* One-liner function *)
test Shellvars.lns get "MyFunc() { echo; }\n" =
  { "@function" = "MyFunc"
    { "@command" = "echo" }
  }

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Shellvars: add systemd's /etc/os-release file

(*
Module: Shellvars
 Generic lens for shell-script config files like the ones found
 in /etc/sysconfig

About: License
   This file is licenced under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

  let eol = del /[ \t]+|[ \t]*[;\n]/ "\n"
  let semicol_eol = del /[ \t]*[;\n]/ "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - "unset" - "export"
  let eq = Util.del_str "="

  let comment = Util.comment
  let comment_eol = Util.comment_eol
  let comment_or_eol = comment_eol | semicol_eol

  let empty   = Util.empty
  let xchgs   = Build.xchgs
  let semicol = del /;?/ ""

  let char  = /[^;#() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  (* For some reason, `` conflicts with comment_or_eol *)
  let bquot = /`[^#`\n]*`/
  let dollar_assign = /\$\([^#`\n]*\)/

  let sto_to_semicol = store /[^#; \t\n][^#;\n]+[^#; \t\n]|[^#; \t\n]+/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del /\([ \t]*/ "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t\n]+/ " " . seq "values" . array_value ] *
      . del /[ \t]*\)/ ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
      store (char* | dquot | squot | bquot | dollar_assign | empty_array)

  let export = [ key "export" . Util.del_ws_spc ]
  let kv = [ Util.indent . export? . key key_re
           . eq . (simple_value | array) . semicol . comment_or_eol ]

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store key_re . semicol . comment_or_eol ]

  let unset = var_action "unset"
  let bare_export = var_action "export"

  let source =
    [ Util.indent
      . del /\.|source/ "." . label ".source"
      . Util.del_ws_spc . store /[^;=# \t\n]+/ . comment_or_eol ]

  let shell_builtin_cmds = "ulimit"

  let builtin =
    [ Util.indent . label "@builtin"
      . store shell_builtin_cmds
      . Util.del_ws_spc
      . [ label "args" . sto_to_semicol ]
      . comment_or_eol ]

  let keyword (kw:string) = Util.indent . Util.del_str kw
  let keyword_label (kw:string) (lbl:string) = keyword kw . label lbl

  let return =
    [ Util.indent . label "@return"
      . Util.del_str "return"
      . ( Util.del_ws_spc . store Rx.integer )?
      . comment_or_eol ]

(************************************************************************
 * Group:                 CONDITIONALS AND LOOPS
 *************************************************************************)

  let generic_cond_start (start_kw:string) (lbl:string)
                         (then_kw:string) (contents:lens) =
      keyword_label start_kw lbl . Sep.space
      . sto_to_semicol . semicol_eol
      . keyword then_kw . eol
      . contents

  let generic_cond (start_kw:string) (lbl:string)
                       (then_kw:string) (contents:lens) (end_kw:string) =
      [ generic_cond_start start_kw lbl then_kw contents
        . keyword end_kw . comment_or_eol ]

  let cond_if (entry:lens) =
    let elif = [ generic_cond_start "elif" "@elif" "then" entry+ ] in
    let else = [ keyword_label "else" "@else" . eol . entry+ ] in
    generic_cond "if" "@if" "then" (entry+ . elif* . else?) "fi"

  let loop_for (entry:lens) =
    generic_cond "for" "@for" "do" entry+ "done"

  let loop_while (entry:lens) =
    generic_cond "while" "@while" "do" entry+ "done"

  let loop_until (entry:lens) =
    generic_cond "until" "@until" "do" entry+ "done"

  let loop_select (entry:lens) =
    generic_cond "select" "@select" "do" entry+ "done"

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry+
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store char+
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . case_entry+
        . keyword "esac" . comment_or_eol ]

  let function (entry:lens) =
    [ Util.indent . label "@function"
      . del /(function[ \t]+)?/ ""
      . store Rx.word . del /[ \t]*\(\)/ "()"
      . eol . Util.del_str "{" . eol
      . entry+
      . Util.indent . Util.del_str "}" . eol ]

  let rec rec_entry =
    let entry = comment | empty | source | kv
              | unset | bare_export | builtin | return | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry
      | function entry

  let lns = (comment | empty | source | kv | unset | bare_export | builtin | return | rec_entry) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let sc_excl (n:string) = (excl ("/etc/sysconfig/" . n))

  let filter_sysconfig =
      sc_incl "*" .
      sc_excl "hw-uuid" .
      sc_excl "hwconf" .
      sc_excl "ip*tables" .
      sc_excl "kernel" .
      sc_excl "*.pub" .
      sc_excl "sysstat.ioconf" .
      sc_excl "system-config-firewall" .
      sc_excl "system-config-securitylevel" .
      sc_incl "network/config" .
      sc_incl "network/dhcp" .
      sc_incl "network/dhcp6r" .
      sc_incl "network/dhcp6s" .
      sc_incl "network/ifcfg-*" .
      sc_incl "network/if-down.d/*" .
      sc_incl "network/ifroute-*" .
      sc_incl "network/if-up.d/*" .
      sc_incl "network/providers/*" .
      sc_incl "rhn/allowed-actions/*" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_ifcfg   = incl "/etc/sysconfig/network-scripts/ifcfg-*"
                     . incl "/etc/sysconfig/network/ifcfg-*"
  let filter_default = incl "/etc/default/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"

  let filter = filter_sysconfig
             . filter_ifcfg
             . filter_default
             . filter_misc
             . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(*
Module: Shellvars
 Generic lens for shell-script config files like the ones found
 in /etc/sysconfig

About: License
   This file is licenced under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

  let eol = del /[ \t]+|[ \t]*[;\n]/ "\n"
  let semicol_eol = del /[ \t]*[;\n]/ "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - "unset" - "export"
  let eq = Util.del_str "="

  let comment = Util.comment
  let comment_eol = Util.comment_eol
  let comment_or_eol = comment_eol | semicol_eol

  let empty   = Util.empty
  let xchgs   = Build.xchgs
  let semicol = del /;?/ ""

  let char  = /[^;#() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  (* For some reason, `` conflicts with comment_or_eol *)
  let bquot = /`[^#`\n]*`/
  let dollar_assign = /\$\([^#`\n]*\)/

  let sto_to_semicol = store /[^#; \t\n][^#;\n]+[^#; \t\n]|[^#; \t\n]+/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del /\([ \t]*/ "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t\n]+/ " " . seq "values" . array_value ] *
      . del /[ \t]*\)/ ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
      store (char* | dquot | squot | bquot | dollar_assign | empty_array)

  let export = [ key "export" . Util.del_ws_spc ]
  let kv = [ Util.indent . export? . key key_re
           . eq . (simple_value | array) . semicol . comment_or_eol ]

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store key_re . semicol . comment_or_eol ]

  let unset = var_action "unset"
  let bare_export = var_action "export"

  let source =
    [ Util.indent
      . del /\.|source/ "." . label ".source"
      . Util.del_ws_spc . store /[^;=# \t\n]+/ . comment_or_eol ]

  let shell_builtin_cmds = "ulimit"

  let builtin =
    [ Util.indent . label "@builtin"
      . store shell_builtin_cmds
      . Util.del_ws_spc
      . [ label "args" . sto_to_semicol ]
      . comment_or_eol ]

  let keyword (kw:string) = Util.indent . Util.del_str kw
  let keyword_label (kw:string) (lbl:string) = keyword kw . label lbl

  let return =
    [ Util.indent . label "@return"
      . Util.del_str "return"
      . ( Util.del_ws_spc . store Rx.integer )?
      . comment_or_eol ]

(************************************************************************
 * Group:                 CONDITIONALS AND LOOPS
 *************************************************************************)

  let generic_cond_start (start_kw:string) (lbl:string)
                         (then_kw:string) (contents:lens) =
      keyword_label start_kw lbl . Sep.space
      . sto_to_semicol . semicol_eol
      . keyword then_kw . eol
      . contents

  let generic_cond (start_kw:string) (lbl:string)
                       (then_kw:string) (contents:lens) (end_kw:string) =
      [ generic_cond_start start_kw lbl then_kw contents
        . keyword end_kw . comment_or_eol ]

  let cond_if (entry:lens) =
    let elif = [ generic_cond_start "elif" "@elif" "then" entry+ ] in
    let else = [ keyword_label "else" "@else" . eol . entry+ ] in
    generic_cond "if" "@if" "then" (entry+ . elif* . else?) "fi"

  let loop_for (entry:lens) =
    generic_cond "for" "@for" "do" entry+ "done"

  let loop_while (entry:lens) =
    generic_cond "while" "@while" "do" entry+ "done"

  let loop_until (entry:lens) =
    generic_cond "until" "@until" "do" entry+ "done"

  let loop_select (entry:lens) =
    generic_cond "select" "@select" "do" entry+ "done"

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry+
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store char+
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . case_entry+
        . keyword "esac" . comment_or_eol ]

  let function (entry:lens) =
    [ Util.indent . label "@function"
      . del /(function[ \t]+)?/ ""
      . store Rx.word . del /[ \t]*\(\)/ "()"
      . eol . Util.del_str "{" . eol
      . entry+
      . Util.indent . Util.del_str "}" . eol ]

  let rec rec_entry =
    let entry = comment | empty | source | kv
              | unset | bare_export | builtin | return | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry
      | function entry

  let lns = (comment | empty | source | kv | unset | bare_export | builtin | return | rec_entry) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let sc_excl (n:string) = (excl ("/etc/sysconfig/" . n))

  let filter_sysconfig =
      sc_incl "*" .
      sc_excl "hw-uuid" .
      sc_excl "hwconf" .
      sc_excl "ip*tables" .
      sc_excl "kernel" .
      sc_excl "*.pub" .
      sc_excl "sysstat.ioconf" .
      sc_excl "system-config-firewall" .
      sc_excl "system-config-securitylevel" .
      sc_incl "network/config" .
      sc_incl "network/dhcp" .
      sc_incl "network/dhcp6r" .
      sc_incl "network/dhcp6s" .
      sc_incl "network/ifcfg-*" .
      sc_incl "network/if-down.d/*" .
      sc_incl "network/ifroute-*" .
      sc_incl "network/if-up.d/*" .
      sc_incl "network/providers/*" .
      sc_incl "rhn/allowed-actions/*" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_ifcfg   = incl "/etc/sysconfig/network-scripts/ifcfg-*"
                     . incl "/etc/sysconfig/network/ifcfg-*"
  let filter_default = incl "/etc/default/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"

  let filter = filter_sysconfig
             . filter_ifcfg
             . filter_default
             . filter_misc
             . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Add seccomp_sandbox to vsftpd.aug lens

Add seccomp_sandbox to vsftpd.aug lens

Required for Docker containers. Enabled by default on amd64
https://wiki.gentoo.org/wiki/Vsftpd
seccomp filter sanboxing with vsftpd 3.0.x

(* Parse vsftpd.conf *)
module Vsftpd =
  autoload xfm

(* The code in parseconf.c does not seem to allow for trailing whitespace *)
(* in the config file                                                     *)
let eol = Util.del_str "\n"
let empty = Util.empty
let comment = Util.comment

let bool_option_re = /anonymous_enable|isolate|isolate_network|local_enable|pasv_enable|port_enable|chroot_local_user|write_enable|anon_upload_enable|anon_mkdir_write_enable|anon_other_write_enable|chown_uploads|connect_from_port_20|xferlog_enable|dirmessage_enable|anon_world_readable_only|async_abor_enable|ascii_upload_enable|ascii_download_enable|one_process_model|xferlog_std_format|pasv_promiscuous|deny_email_enable|chroot_list_enable|setproctitle_enable|text_userdb_names|ls_recurse_enable|log_ftp_protocol|guest_enable|userlist_enable|userlist_deny|use_localtime|check_shell|hide_ids|listen|port_promiscuous|passwd_chroot_enable|no_anon_password|tcp_wrappers|use_sendfile|force_dot_files|listen_ipv6|dual_log_enable|syslog_enable|background|virtual_use_local_privs|session_support|download_enable|dirlist_enable|chmod_enable|secure_email_list_enable|run_as_launching_user|no_log_lock|ssl_enable|allow_anon_ssl|force_local_logins_ssl|force_local_data_ssl|ssl_sslv2|ssl_sslv3|ssl_tlsv1|tilde_user_enable|force_anon_logins_ssl|force_anon_data_ssl|mdtm_write|lock_upload_files|pasv_addr_resolve|debug_ssl|require_cert|validate_cert|require_ssl_reuse|allow_writeable_chroot|seccomp_sandbox/

let uint_option_re = /accept_timeout|connect_timeout|local_umask|anon_umask|ftp_data_port|idle_session_timeout|data_connection_timeout|pasv_min_port|pasv_max_port|anon_max_rate|local_max_rate|listen_port|max_clients|file_open_mode|max_per_ip|trans_chunk_size|delay_failed_login|delay_successful_login|max_login_fails|chown_upload_mode/

let str_option_re = /secure_chroot_dir|ftp_username|chown_username|xferlog_file|vsftpd_log_file|message_file|nopriv_user|ftpd_banner|banned_email_file|chroot_list_file|pam_service_name|guest_username|userlist_file|anon_root|local_root|banner_file|pasv_address|listen_address|user_config_dir|listen_address6|cmds_allowed|hide_file|deny_file|user_sub_token|email_password_file|rsa_cert_file|dsa_cert_file|ssl_ciphers|rsa_private_key_file|dsa_private_key_file|ca_certs_file/

let bool_value_re = /[yY][eE][sS]|[tT][rR][uU][eE]|1|[nN][oO]|[fF][aA][lL][sS][eE]|0/

let option (k:regexp) (v:regexp) = [ key k . Util.del_str "=" . store v . eol ]

let bool_option = option bool_option_re bool_value_re

let str_option = option str_option_re /[^\n]+/

let uint_option = option uint_option_re /[0-9]+/

let lns = (bool_option|str_option|uint_option|comment|empty)*

let filter = (incl "/etc/vsftpd.conf") . (incl "/etc/vsftpd/vsftpd.conf")

let xfm = transform lns filter

(* Parse vsftpd.conf *)
module Vsftpd =
  autoload xfm

(* The code in parseconf.c does not seem to allow for trailing whitespace *)
(* in the config file                                                     *)
let eol = Util.del_str "\n"
let empty = Util.empty
let comment = Util.comment

let bool_option_re = /anonymous_enable|isolate|isolate_network|local_enable|pasv_enable|port_enable|chroot_local_user|write_enable|anon_upload_enable|anon_mkdir_write_enable|anon_other_write_enable|chown_uploads|connect_from_port_20|xferlog_enable|dirmessage_enable|anon_world_readable_only|async_abor_enable|ascii_upload_enable|ascii_download_enable|one_process_model|xferlog_std_format|pasv_promiscuous|deny_email_enable|chroot_list_enable|setproctitle_enable|text_userdb_names|ls_recurse_enable|log_ftp_protocol|guest_enable|userlist_enable|userlist_deny|use_localtime|check_shell|hide_ids|listen|port_promiscuous|passwd_chroot_enable|no_anon_password|tcp_wrappers|use_sendfile|force_dot_files|listen_ipv6|dual_log_enable|syslog_enable|background|virtual_use_local_privs|session_support|download_enable|dirlist_enable|chmod_enable|secure_email_list_enable|run_as_launching_user|no_log_lock|ssl_enable|allow_anon_ssl|force_local_logins_ssl|force_local_data_ssl|ssl_sslv2|ssl_sslv3|ssl_tlsv1|tilde_user_enable|force_anon_logins_ssl|force_anon_data_ssl|mdtm_write|lock_upload_files|pasv_addr_resolve|debug_ssl|require_cert|validate_cert|require_ssl_reuse|allow_writeable_chroot/

let uint_option_re = /accept_timeout|connect_timeout|local_umask|anon_umask|ftp_data_port|idle_session_timeout|data_connection_timeout|pasv_min_port|pasv_max_port|anon_max_rate|local_max_rate|listen_port|max_clients|file_open_mode|max_per_ip|trans_chunk_size|delay_failed_login|delay_successful_login|max_login_fails|chown_upload_mode/

let str_option_re = /secure_chroot_dir|ftp_username|chown_username|xferlog_file|vsftpd_log_file|message_file|nopriv_user|ftpd_banner|banned_email_file|chroot_list_file|pam_service_name|guest_username|userlist_file|anon_root|local_root|banner_file|pasv_address|listen_address|user_config_dir|listen_address6|cmds_allowed|hide_file|deny_file|user_sub_token|email_password_file|rsa_cert_file|dsa_cert_file|ssl_ciphers|rsa_private_key_file|dsa_private_key_file|ca_certs_file/

let bool_value_re = /[yY][eE][sS]|[tT][rR][uU][eE]|1|[nN][oO]|[fF][aA][lL][sS][eE]|0/

let option (k:regexp) (v:regexp) = [ key k . Util.del_str "=" . store v . eol ]

let bool_option = option bool_option_re bool_value_re

let str_option = option str_option_re /[^\n]+/

let uint_option = option uint_option_re /[0-9]+/

let lns = (bool_option|str_option|uint_option|comment|empty)*

let filter = (incl "/etc/vsftpd.conf") . (incl "/etc/vsftpd/vsftpd.conf")

let xfm = transform lns filter


Util: Add comment_c_style_or_hash

Util: Add comment_c_style_or_hash

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.
*)

module Util =

(*
Variable: del_str
  Delete a string and default to it

  Parameters:
     s:string - the string to delete and default to
*)
  let del_str (s:string) = del s s

(*
Variable: del_ws
  Delete mandatory whitespace
*)
  let del_ws = del /[ \t]+/

(*
Variable: del_ws_spc
  Delete mandatory whitespace, default to single space
*)
  let del_ws_spc = del_ws " "

(*
Variable: del_ws_tab
  Delete mandatory whitespace, default to single tab
*)
  let del_ws_tab = del_ws "\t"

(*
Variable: del_opt_ws
  Delete optional whitespace
*)
  let del_opt_ws = del /[ \t]*/

(*
Variable: eol
  Delete end of line, including optional trailing whitespace
*)
  let eol = del /[ \t]*\n/ "\n"

(*
Variable: doseol
  Delete end of line with optional carriage return,
  including optional trailing whitespace
*)
  let doseol = del /[ \t]*\r?\n/ "\n"

(*
Variable: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment
     This is a general definition of comment
     It allows indentation for comments, removes the leading and trailing spaces
     of comments and stores them in nodes, except for empty comments which are
     ignored together with empty lines
*)

(* View: comment_generic_seteol
  Map comments and set default comment sign
*)

  let comment_generic_seteol (r:regexp) (d:string) (eol:lens) =
    [ label "#comment" . del r d
        . store /([^ \t\r\n].*[^ \t\r\n]|[^ \t\r\n])/ . eol ]

(* View: comment_generic
  Map comments and set default comment sign
*)

  let comment_generic (r:regexp) (d:string) =
    comment_generic_seteol r d doseol

(* View: comment
  Map comments into "#comment" nodes
*)
  let comment = comment_generic /[ \t]*#[ \t]*/ "# "

(* View: comment_noindent
  Map comments into "#comment" nodes, without indentation
*)
  let comment_noindent = comment_generic /#[ \t]*/ "# "

(* View: comment_eol
  Map eol comments into "#comment" nodes
  Add a space before # for end of line comments
*)
  let comment_eol = comment_generic /[ \t]*#[ \t]*/ " # "

(* View: comment_or_eol
    A <comment_eol> or <eol>, with an optional empty comment *)
 let comment_or_eol = comment_eol | (del /[ \t]*(#[ \t]*)?\n/ "\n")

(* View: comment_multiline
    A C-style multiline comment *)
  let comment_multiline =
     let mline_re = (/[^ \t\r\n].*[^ \t\r\n]|[^ \t\r\n]/ - /.*\*\/.*/) in
     let mline = [ seq "mline"
                 . del /[ \t\r\n]*/ "\n"
                 . store mline_re ] in
     [ label "#mcomment" . del /[ \t]*\/\*/ "/*"
       . counter "mline"
       . mline . (eol . mline)*
       . del /[ \t\r\n]*\*\/[ \t]*\r?\n/ "\n*/\n" ]

(* View: comment_c_style
    A comment line, C-style *)
  let comment_c_style =
    comment_generic /[ \t]*\/\/[ \t]*/ "// "

(* View: comment_c_style_or_hash
    A comment line, C-style or hash *)
  let comment_c_style_or_hash =
    comment_generic /[ \t]*((\/\/)|#)[ \t]*/ "// "

(* View: empty_generic
  A generic definition of <empty>
  Map empty lines, including empty comments *)
  let empty_generic (r:regexp) =
    [ del r "" . del_str "\n" ]

(* Variable: empty_generic_re *)
  let empty_generic_re = /[ \t]*#?[ \t]*/

(* View: empty
  Map empty lines, including empty comments *)
  let empty = empty_generic empty_generic_re

(* View: empty_c_style
  Map empty lines, including C-style empty comment *)
  let empty_c_style =
    empty_generic /[ \t]*((\/\/)|(\/\*[ \t]*\*\/))?[ \t]*/

(* View: empty_generic_dos
  A generic definition of <empty> with dos newlines
  Map empty lines, including empty comments *)
  let empty_generic_dos (r:regexp) =
    [ del r "" . del /\r?\n/ "\n" ]

(* View: empty_dos *)
  let empty_dos =
    empty_generic_dos /[ \t]*#?[ \t]*/

(* View: Split *)
(* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
(* appears in a separate subtree. The labels for the subtrees are      *)
(* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

(* View: delim *)
  let delim (op:string) = del (/[ \t]*/ . op . /[ \t]*/)
                              (" " . op . " ")

(* Group: Exclusions

Variable: stdexcl
  Exclusion for files that are commonly not wanted/needed
*)
  let stdexcl = (excl "*~") .
    (excl "*.rpmnew") .
    (excl "*.rpmsave") .
    (excl "*.dpkg-old") .
    (excl "*.dpkg-new") .
    (excl "*.dpkg-bak") .
    (excl "*.dpkg-dist") .
    (excl "*.augsave") .
    (excl "*.augnew") .
    (excl "*.bak") .
    (excl "*.old") .
    (excl "#*#")

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.
*)

module Util =

(*
Variable: del_str
  Delete a string and default to it

  Parameters:
     s:string - the string to delete and default to
*)
  let del_str (s:string) = del s s

(*
Variable: del_ws
  Delete mandatory whitespace
*)
  let del_ws = del /[ \t]+/

(*
Variable: del_ws_spc
  Delete mandatory whitespace, default to single space
*)
  let del_ws_spc = del_ws " "

(*
Variable: del_ws_tab
  Delete mandatory whitespace, default to single tab
*)
  let del_ws_tab = del_ws "\t"

(*
Variable: del_opt_ws
  Delete optional whitespace
*)
  let del_opt_ws = del /[ \t]*/

(*
Variable: eol
  Delete end of line, including optional trailing whitespace
*)
  let eol = del /[ \t]*\n/ "\n"

(*
Variable: doseol
  Delete end of line with optional carriage return,
  including optional trailing whitespace
*)
  let doseol = del /[ \t]*\r?\n/ "\n"

(*
Variable: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment
     This is a general definition of comment
     It allows indentation for comments, removes the leading and trailing spaces
     of comments and stores them in nodes, except for empty comments which are
     ignored together with empty lines
*)

(* View: comment_generic_seteol
  Map comments and set default comment sign
*)

  let comment_generic_seteol (r:regexp) (d:string) (eol:lens) =
    [ label "#comment" . del r d
        . store /([^ \t\r\n].*[^ \t\r\n]|[^ \t\r\n])/ . eol ]

(* View: comment_generic
  Map comments and set default comment sign
*)

  let comment_generic (r:regexp) (d:string) =
    comment_generic_seteol r d doseol

(* View: comment
  Map comments into "#comment" nodes
*)
  let comment = comment_generic /[ \t]*#[ \t]*/ "# "

(* View: comment_noindent
  Map comments into "#comment" nodes, without indentation
*)
  let comment_noindent = comment_generic /#[ \t]*/ "# "

(* View: comment_eol
  Map eol comments into "#comment" nodes
  Add a space before # for end of line comments
*)
  let comment_eol = comment_generic /[ \t]*#[ \t]*/ " # "

(* View: comment_or_eol
    A <comment_eol> or <eol>, with an optional empty comment *)
 let comment_or_eol = comment_eol | (del /[ \t]*(#[ \t]*)?\n/ "\n")

(* View: comment_multiline
    A C-style multiline comment *)
  let comment_multiline =
     let mline_re = (/[^ \t\r\n].*[^ \t\r\n]|[^ \t\r\n]/ - /.*\*\/.*/) in
     let mline = [ seq "mline"
                 . del /[ \t\r\n]*/ "\n"
                 . store mline_re ] in
     [ label "#mcomment" . del /[ \t]*\/\*/ "/*"
       . counter "mline"
       . mline . (eol . mline)*
       . del /[ \t\r\n]*\*\/[ \t]*\r?\n/ "\n*/\n" ]

(* View: comment_c_style
    A comment line, C-style *)
  let comment_c_style =
    comment_generic /[ \t]*\/\/[ \t]*/ "// "

(* View: empty_generic
  A generic definition of <empty>
  Map empty lines, including empty comments *)
  let empty_generic (r:regexp) =
    [ del r "" . del_str "\n" ]

(* Variable: empty_generic_re *)
  let empty_generic_re = /[ \t]*#?[ \t]*/

(* View: empty
  Map empty lines, including empty comments *)
  let empty = empty_generic empty_generic_re

(* View: empty_c_style
  Map empty lines, including C-style empty comment *)
  let empty_c_style =
    empty_generic /[ \t]*((\/\/)|(\/\*[ \t]*\*\/))?[ \t]*/

(* View: empty_generic_dos
  A generic definition of <empty> with dos newlines
  Map empty lines, including empty comments *)
  let empty_generic_dos (r:regexp) =
    [ del r "" . del /\r?\n/ "\n" ]

(* View: empty_dos *)
  let empty_dos =
    empty_generic_dos /[ \t]*#?[ \t]*/

(* View: Split *)
(* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
(* appears in a separate subtree. The labels for the subtrees are      *)
(* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

(* View: delim *)
  let delim (op:string) = del (/[ \t]*/ . op . /[ \t]*/)
                              (" " . op . " ")

(* Group: Exclusions

Variable: stdexcl
  Exclusion for files that are commonly not wanted/needed
*)
  let stdexcl = (excl "*~") .
    (excl "*.rpmnew") .
    (excl "*.rpmsave") .
    (excl "*.dpkg-old") .
    (excl "*.dpkg-new") .
    (excl "*.dpkg-bak") .
    (excl "*.dpkg-dist") .
    (excl "*.augsave") .
    (excl "*.augnew") .
    (excl "*.bak") .
    (excl "*.old") .
    (excl "#*#")


Add FreeBSD /etc/rc.conf file to Shellvars lens.

(* Generic lens for shell-script config files like the ones found *)
(* in /etc/sysconfig                                              *)
module Shellvars =
  autoload xfm

  let eol = Util.eol

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - "unset" - "export"
  let eq = Util.del_str "="
  let comment = Util.comment
  let comment_or_eol = Util.comment_or_eol
  let empty   = Util.empty
  let xchgs   = Build.xchgs

  let char  = /[^#() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  (* For some reason, `` conflicts with comment_or_eol *)
  let bquot = /`[^#`\n]*`/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del /\([ \t]*/ "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t\n]+/ " " . seq "values" . array_value ] *
      . del /[ \t]*\)/ ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
      store (char* | dquot | squot | bquot | empty_array)

  let export = [ key "export" . Util.del_ws_spc ]
  let kv = [ export? . key key_re . eq . (simple_value | array) . comment_or_eol ]

  let var_action (name:string) =
    [ xchgs name ("@" . name) . Util.del_ws_spc . store key_re . comment_or_eol ]

  let unset = var_action "unset"
  let bare_export = var_action "export"

  let source =
    [
      del /\.|source/ "." . label ".source" .
      Util.del_ws_spc . store /[^= \t\n]+/ . eol
    ]

  let shell_builtin_cmds = "ulimit"

  let builtin =
    [ label "@builtin"
      . store shell_builtin_cmds
      . Util.del_ws_spc
      . [ label "args" . store /[^ \t\n][^;\n]+[^ \t\n]|[^ \t;\n]+/ ]
      . eol ]

  let lns = (comment | empty | source | kv | unset | bare_export | builtin) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let filter_sysconfig =
      sc_incl "atd" .
      sc_incl "authconfig" .
      sc_incl "autofs" .
      sc_incl "clock" .
      sc_incl "cpuspeed" .
      sc_incl "crond" .
      sc_incl "crontab" .
      sc_incl "desktop" .
      sc_incl "firstboot" .
      sc_incl "grub" .
      sc_incl "hsqldb" .
      sc_incl "httpd" .
      sc_incl "i18n" .
      sc_incl "init" .
      sc_incl "iptables-config" .
      sc_incl "irda" .
      sc_incl "irqbalance" .
      sc_incl "kdump" .
      sc_incl "keyboard" .
      sc_incl "kudzu" .
      sc_incl "libvirtd" .
      sc_incl "lircd" .
      sc_incl "nasd" .
      sc_incl "netconsole" .
      sc_incl "network" .
      sc_incl "nfs" .
      sc_incl "ntpd" .
      sc_incl "prelink" .
      sc_incl "puppet" .
      sc_incl "puppetmaster" .
      sc_incl "readonly-root" .
      sc_incl "rsyslog" .
      sc_incl "samba" .
      sc_incl "saslauthd" .
      sc_incl "selinux" .
      sc_incl "sendmail" .
      sc_incl "smartmontools" .
      sc_incl "snmpd" .
      sc_incl "snmpd.options" .
      sc_incl "snmptrapd" .
      sc_incl "snmptrapd.options" .
      sc_incl "spamassassin" .
      sc_incl "suseconfig" .
      sc_incl "sysstat" .
      sc_incl "system-config-users" .
      sc_incl "vncservers" .
      sc_incl "wpa_supplicant" .
      sc_incl "xend" .
      sc_incl "xendomains"

  let filter_ifcfg   = incl "/etc/sysconfig/network-scripts/ifcfg-*"
                     . incl "/etc/sysconfig/network/ifcfg-*"
  let filter_default = incl "/etc/default/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/rc.conf"

  let filter = filter_sysconfig
             . filter_ifcfg
             . filter_default
             . filter_misc
             . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Generic lens for shell-script config files like the ones found *)
(* in /etc/sysconfig                                              *)
module Shellvars =
  autoload xfm

  let eol = Util.eol

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - "unset" - "export"
  let eq = Util.del_str "="
  let comment = Util.comment
  let comment_or_eol = Util.comment_or_eol
  let empty   = Util.empty
  let xchgs   = Build.xchgs

  let char  = /[^#() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  (* For some reason, `` conflicts with comment_or_eol *)
  let bquot = /`[^#`\n]*`/

  (* Array values of the form '(val1 val2 val3)'. We do not handle empty *)
  (* arrays here because of typechecking headaches. Instead, they are    *)
  (* treated as a simple value                                           *)
  let array =
    let array_value = store (char+ | dquot) in
    del /\([ \t]*/ "(" . counter "values" .
      [ seq "values" . array_value ] .
      [ del /[ \t\n]+/ " " . seq "values" . array_value ] *
      . del /[ \t]*\)/ ")"

  (* Treat an empty list () as a value '()'; that's not quite correct *)
  (* but fairly close.                                                *)
  let simple_value =
    let empty_array = /\([ \t]*\)/ in
      store (char* | dquot | squot | bquot | empty_array)

  let export = [ key "export" . Util.del_ws_spc ]
  let kv = [ export? . key key_re . eq . (simple_value | array) . comment_or_eol ]

  let var_action (name:string) =
    [ xchgs name ("@" . name) . Util.del_ws_spc . store key_re . comment_or_eol ]

  let unset = var_action "unset"
  let bare_export = var_action "export"

  let source =
    [
      del /\.|source/ "." . label ".source" .
      Util.del_ws_spc . store /[^= \t\n]+/ . eol
    ]

  let shell_builtin_cmds = "ulimit"

  let builtin =
    [ label "@builtin"
      . store shell_builtin_cmds
      . Util.del_ws_spc
      . [ label "args" . store /[^ \t\n][^;\n]+[^ \t\n]|[^ \t;\n]+/ ]
      . eol ]

  let lns = (comment | empty | source | kv | unset | bare_export | builtin) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let filter_sysconfig =
      sc_incl "atd" .
      sc_incl "authconfig" .
      sc_incl "autofs" .
      sc_incl "clock" .
      sc_incl "cpuspeed" .
      sc_incl "crond" .
      sc_incl "crontab" .
      sc_incl "desktop" .
      sc_incl "firstboot" .
      sc_incl "grub" .
      sc_incl "hsqldb" .
      sc_incl "httpd" .
      sc_incl "i18n" .
      sc_incl "init" .
      sc_incl "iptables-config" .
      sc_incl "irda" .
      sc_incl "irqbalance" .
      sc_incl "kdump" .
      sc_incl "keyboard" .
      sc_incl "kudzu" .
      sc_incl "libvirtd" .
      sc_incl "lircd" .
      sc_incl "nasd" .
      sc_incl "netconsole" .
      sc_incl "network" .
      sc_incl "nfs" .
      sc_incl "ntpd" .
      sc_incl "prelink" .
      sc_incl "puppet" .
      sc_incl "puppetmaster" .
      sc_incl "readonly-root" .
      sc_incl "rsyslog" .
      sc_incl "samba" .
      sc_incl "saslauthd" .
      sc_incl "selinux" .
      sc_incl "sendmail" .
      sc_incl "smartmontools" .
      sc_incl "snmpd" .
      sc_incl "snmpd.options" .
      sc_incl "snmptrapd" .
      sc_incl "snmptrapd.options" .
      sc_incl "spamassassin" .
      sc_incl "suseconfig" .
      sc_incl "sysstat" .
      sc_incl "system-config-users" .
      sc_incl "vncservers" .
      sc_incl "wpa_supplicant" .
      sc_incl "xend" .
      sc_incl "xendomains"

  let filter_ifcfg   = incl "/etc/sysconfig/network-scripts/ifcfg-*"
                     . incl "/etc/sysconfig/network/ifcfg-*"
  let filter_default = incl "/etc/default/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"

  let filter = filter_sysconfig
             . filter_ifcfg
             . filter_default
             . filter_misc
             . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Nsswitch: add passwd_compat, group_compat and shadow_compat GNU extensions

(*
Module: Nsswitch
  Parses /etc/nsswitch.conf

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
  This lens tries to keep as close as possible to `man nsswitch.conf` where possible.

About: Licence
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage

About: Configuration files
  This lens applies to /etc/nsswitch.conf. See <filter>.
*)

module Nsswitch =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: comment *)
let comment = Util.comment

(* View: empty *)
let empty = Util.empty

(* View: sep_colon
    The separator for database entries *)
let sep_colon = del /:[ \t]*/ ": "

(* View: service
    The service specification like `files', `db', or `nis' *)
let service = [ label "service" . store Rx.word ]

(* View: reaction
    The reaction on lookup result like `[NOTFOUND=return]'
    TODO: Use case-insensitive regexps when ticket #147 is fixed.
*)
let reaction =
  let status_kw = /[Ss][Uu][Cc][Cc][Ee][Ss][Ss]/
                | /[Nn][Oo][Tt][Ff][Oo][Uu][Nn][Dd]/
                | /[Uu][Nn][Aa][Vv][Aa][Ii][Ll]/
                | /[Tt][Rr][Yy][Aa][Gg][Aa][Ii][Nn]/
    in let action_kw = /[Rr][Ee][Tt][Uu][Rr][Nn]/
                     | /[Cc][Oo][Nn][Tt][Ii][Nn][Uu][Ee]/
      in let negate = [ Util.del_str "!" . label "negate" ]
        in let reaction_entry = [ label "status" . negate?
                                . store status_kw
                                . Util.del_str "="
                                . [ label "action" . store action_kw ] ]
          in Util.del_str "["
             . [ label "reaction"
               . (Build.opt_list reaction_entry Sep.space) ]
             . Util.del_str "]"

(* View: database *)
let database =
  let database_kw = "aliases"
                  | "automount"
                  | "bootparams"
                  | "ethers"
                  | "group"
                  | "hosts"
                  | "netgroup"
                  | "netmasks"
                  | "networks"
                  | "passwd"
                  | "protocols"
                  | "publickey"
                  | "rpc"
                  | "sendmailvars"
                  | "services"
                  | "shadow"
                  | "sudoers"
                  | "passwd_compat"
                  | "group_compat"
                  | "shadow_compat"
    in [ label "database" . store database_kw
               . sep_colon
               . (Build.opt_list
                    (service|reaction)
                    Sep.space)
               . Util.eol ]

(* View: lns *)
let lns = ( empty | comment | database )*

(* Variable: filter *)
let filter = (incl "/etc/nsswitch.conf")
    . Util.stdexcl

let xfm = transform lns filter

(*
Module: Nsswitch
  Parses /etc/nsswitch.conf

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
  This lens tries to keep as close as possible to `man nsswitch.conf` where possible.

About: Licence
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage

About: Configuration files
  This lens applies to /etc/nsswitch.conf. See <filter>.
*)

module Nsswitch =
autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* View: comment *)
let comment = Util.comment

(* View: empty *)
let empty = Util.empty

(* View: sep_colon
    The separator for database entries *)
let sep_colon = del /:[ \t]*/ ": "

(* View: service
    The service specification like `files', `db', or `nis' *)
let service = [ label "service" . store Rx.word ]

(* View: reaction
    The reaction on lookup result like `[NOTFOUND=return]'
    TODO: Use case-insensitive regexps when ticket #147 is fixed.
*)
let reaction =
  let status_kw = /[Ss][Uu][Cc][Cc][Ee][Ss][Ss]/
                | /[Nn][Oo][Tt][Ff][Oo][Uu][Nn][Dd]/
                | /[Uu][Nn][Aa][Vv][Aa][Ii][Ll]/
                | /[Tt][Rr][Yy][Aa][Gg][Aa][Ii][Nn]/
    in let action_kw = /[Rr][Ee][Tt][Uu][Rr][Nn]/
                     | /[Cc][Oo][Nn][Tt][Ii][Nn][Uu][Ee]/
      in let negate = [ Util.del_str "!" . label "negate" ]
        in let reaction_entry = [ label "status" . negate?
                                . store status_kw
                                . Util.del_str "="
                                . [ label "action" . store action_kw ] ]
          in Util.del_str "["
             . [ label "reaction"
               . (Build.opt_list reaction_entry Sep.space) ]
             . Util.del_str "]"

(* View: database *)
let database =
  let database_kw = "aliases"
                  | "automount"
                  | "bootparams"
                  | "ethers"
                  | "group"
                  | "hosts"
                  | "netgroup"
                  | "netmasks"
                  | "networks"
                  | "passwd"
                  | "protocols"
                  | "publickey"
                  | "rpc"
                  | "sendmailvars"
                  | "services"
                  | "shadow"
                  | "sudoers"
    in [ label "database" . store database_kw
               . sep_colon
               . (Build.opt_list
                    (service|reaction)
                    Sep.space)
               . Util.eol ]

(* View: lns *)
let lns = ( empty | comment | database )*

(* Variable: filter *)
let filter = (incl "/etc/nsswitch.conf")
    . Util.stdexcl

let xfm = transform lns filter


update rsyslog lens to includ files in rsyslog.d dir (#475)

(*
Module: Rsyslog
  Parses /etc/rsyslog.conf

Author: Raphael Pinson <raphael.pinsons@camptocamp.com>

About: Reference
  This lens tries to keep as close as possible to `man 5 rsyslog.conf` where possible.

About: License
   This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/rsyslog.conf. See <filter>.

About: Examples
   The <Test_Rsyslog> file contains various examples and tests.
*)
module Rsyslog =

autoload xfm

let macro_rx = /[^,# \n\t][^#\n]*[^,# \n\t]|[^,# \n\t]/
let macro = [ key /$[A-Za-z0-9]+/ . Sep.space . store macro_rx . Util.comment_or_eol ]

let config_object_param = [ key /[A-Za-z.]+/ . Sep.equal . Quote.dquote
                          . store /[^"]+/ . Quote.dquote ]
let config_object = [ key /action|global|input|module|parser|timezone/ . Sep.lbracket
                    . config_object_param . ( Sep.space . config_object_param )* . Sep.rbracket . Util.comment_or_eol ]

(* View: users
   Map :omusrmsg: and a list of users, or a single *
*)
let omusrmsg = Util.del_str ":omusrmsg:" .
                 Syslog.label_opt_list_or "omusrmsg" (store Syslog.word)
                                          Syslog.comma "*"

(* View: file_tmpl
   File action with a specified template *)
let file_tmpl = Syslog.file . [ label "template" . Util.del_str ";" . store Rx.word ]

let namedpipe = Syslog.pipe . Sep.space . [ label "pipe" . store Syslog.file_r ]

let action = Syslog.action | omusrmsg | file_tmpl | namedpipe

(* View: entry
   An entry contains selectors and an action
*)
let entry = [ label "entry" . Syslog.selectors . Syslog.sep_tab .
              [ label "action" . action ] . Util.eol ]

(* View: prop_filter
   Parses property-based filters, which start with ":" and the property name *)
let prop_filter =
     let sep = Sep.comma . Util.del_opt_ws " "
  in let prop_name = [ Util.del_str ":" . label "property" . store Rx.word ]
  in let prop_oper = [ label "operation" . store /[A-Za-z!-]+/ ]
  in let prop_val  = [ label "value" . Quote.do_dquote (store /[^\n"]*/) ]
  in let prop_act  = [ label "action" . action ]
  in [ label "filter" . prop_name . sep . prop_oper . sep . prop_val .
       Sep.space . prop_act . Util.eol ]

let entries = ( Syslog.empty | Syslog.comment | entry | macro | config_object | prop_filter )*

let lns = entries . ( Syslog.program | Syslog.hostname )*

let filter = incl "/etc/rsyslog.conf"
           . incl "/etc/rsyslog.d/*"
           . Util.stdexcl

let xfm = transform lns filter

(*
Module: Rsyslog
  Parses /etc/rsyslog.conf

Author: Raphael Pinson <raphael.pinsons@camptocamp.com>

About: Reference
  This lens tries to keep as close as possible to `man 5 rsyslog.conf` where possible.

About: License
   This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/rsyslog.conf. See <filter>.

About: Examples
   The <Test_Rsyslog> file contains various examples and tests.
*)
module Rsyslog =

autoload xfm

let macro_rx = /[^,# \n\t][^#\n]*[^,# \n\t]|[^,# \n\t]/
let macro = [ key /$[A-Za-z0-9]+/ . Sep.space . store macro_rx . Util.comment_or_eol ]

let config_object_param = [ key /[A-Za-z.]+/ . Sep.equal . Quote.dquote
                          . store /[^"]+/ . Quote.dquote ]
let config_object = [ key /action|global|input|module|parser|timezone/ . Sep.lbracket
                    . config_object_param . ( Sep.space . config_object_param )* . Sep.rbracket . Util.comment_or_eol ]

(* View: users
   Map :omusrmsg: and a list of users, or a single *
*)
let omusrmsg = Util.del_str ":omusrmsg:" .
                 Syslog.label_opt_list_or "omusrmsg" (store Syslog.word)
                                          Syslog.comma "*"

(* View: file_tmpl
   File action with a specified template *)
let file_tmpl = Syslog.file . [ label "template" . Util.del_str ";" . store Rx.word ]

let namedpipe = Syslog.pipe . Sep.space . [ label "pipe" . store Syslog.file_r ]

let action = Syslog.action | omusrmsg | file_tmpl | namedpipe

(* View: entry
   An entry contains selectors and an action
*)
let entry = [ label "entry" . Syslog.selectors . Syslog.sep_tab .
              [ label "action" . action ] . Util.eol ]

(* View: prop_filter
   Parses property-based filters, which start with ":" and the property name *)
let prop_filter =
     let sep = Sep.comma . Util.del_opt_ws " "
  in let prop_name = [ Util.del_str ":" . label "property" . store Rx.word ]
  in let prop_oper = [ label "operation" . store /[A-Za-z!-]+/ ]
  in let prop_val  = [ label "value" . Quote.do_dquote (store /[^\n"]*/) ]
  in let prop_act  = [ label "action" . action ]
  in [ label "filter" . prop_name . sep . prop_oper . sep . prop_val .
       Sep.space . prop_act . Util.eol ]

let entries = ( Syslog.empty | Syslog.comment | entry | macro | config_object | prop_filter )*

let lns = entries . ( Syslog.program | Syslog.hostname )*

let filter = incl "/etc/rsyslog.conf"

let xfm = transform lns filter


Interfaces: accept options containing '_'

Bug reported by Jonas Eriksson

(* Intefraces module for Augeas
 Author: Free Ekanayaka <free@64studio.com>

 Reference: man interfaces

*)

module Interfaces =
   autoload xfm

(************************************************************************
 *                           USEFUL PRIMITIVES
 *************************************************************************)

let eol        = Util.eol

(* Define separators *)

(* a line can be extended across multiple lines by making the last  *)
(*  character a backslash, unless the line is a comment (see below) *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Define fields *)
let sto_to_eol = store /([^\\# \t\n].*[^\\ \t\n]|[^\\ \t\n])/
let sto_to_spc = store /[^\\# \t\n]+/

(* Define comments and empty lines *)

(* note that the comment definition from Util does not support *)
(* splitting lines with a backlash                             *)
let comment    = Util.comment

let empty      = Util.empty

(* Define tree stanza_ids *)
let stanza_id    (t:string) = key t . sep_spc . sto_to_spc
let stanza_param (l:string) = [ sep_spc . label l . sto_to_spc ]

(* Define reseverved words *)
let stanza_word = /(iface|auto|allow-[a-z-]+|mapping)/

(* Define additional lines for mluti-line stanzas *)
let stanza_option = [  del /[ \t]*/ "   "
                     . key  ( /[a-z_-]+/ - stanza_word )
                     . sep_spc
                     . sto_to_eol
                     . eol ]

(************************************************************************
 *                              AUTO
 *************************************************************************)

let array (r:regexp) (t:string) =  del r t . label t . counter t
   . [ sep_spc . seq t . sto_to_spc ]+
let auto = [ array /(allow-)?auto/ "auto" . (comment|eol) ]

(************************************************************************
 *                              HOTPLUG
 *************************************************************************)

let hotplug = [ stanza_id "allow-hotplug" . (comment|eol) ]

(************************************************************************
 *                              MAPPING
 *************************************************************************)

let mapping = [ stanza_id "mapping"
               . (comment|empty)+
               . stanza_option
               . (stanza_option|comment|empty)* ]

(************************************************************************
 *                              IFACE
 *************************************************************************)

let iface   = [ stanza_id    "iface"
              . stanza_param "family"
              . stanza_param "method"
              . (comment|empty)+
              . ( stanza_option . (stanza_option|comment|empty)* )? ]

(************************************************************************
 *                              STANZAS
 *************************************************************************)

(* The auto and hotplug stanzas always consist of one line only, while
   iface and mapping can spand along more lines. Comment nodes are
   inserted in the tree as direct children of the root node only when they
   come after an auto or hotplug stanza, otherwise they are considered part
   of a iface or mapping block *)

let stanza_single = (auto|hotplug) . (comment|empty)*
let stanza_multi  = iface|mapping

(************************************************************************
 *                              LENS & FILTER
 *************************************************************************)

   let lns = (comment|empty)* . (stanza_multi | stanza_single)*

   let filter = incl "/etc/network/interfaces"
              . Util.stdexcl

   let xfm = transform lns filter

(* Intefraces module for Augeas
 Author: Free Ekanayaka <free@64studio.com>

 Reference: man interfaces

*)

module Interfaces =
   autoload xfm

(************************************************************************
 *                           USEFUL PRIMITIVES
 *************************************************************************)

let eol        = Util.eol

(* Define separators *)

(* a line can be extended across multiple lines by making the last  *)
(*  character a backslash, unless the line is a comment (see below) *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Define fields *)
let sto_to_eol = store /([^\\# \t\n].*[^\\ \t\n]|[^\\ \t\n])/
let sto_to_spc = store /[^\\# \t\n]+/

(* Define comments and empty lines *)

(* note that the comment definition from Util does not support *)
(* splitting lines with a backlash                             *)
let comment    = Util.comment

let empty      = Util.empty

(* Define tree stanza_ids *)
let stanza_id    (t:string) = key t . sep_spc . sto_to_spc
let stanza_param (l:string) = [ sep_spc . label l . sto_to_spc ]

(* Define reseverved words *)
let stanza_word = /(iface|auto|allow-[a-z-]+|mapping)/

(* Define additional lines for mluti-line stanzas *)
let stanza_option = [  del /[ \t]*/ "   "
                     . key  ( /[a-z-]+/ - stanza_word )
                     . sep_spc
                     . sto_to_eol
                     . eol ]

(************************************************************************
 *                              AUTO
 *************************************************************************)

let array (r:regexp) (t:string) =  del r t . label t . counter t
   . [ sep_spc . seq t . sto_to_spc ]+
let auto = [ array /(allow-)?auto/ "auto" . (comment|eol) ]

(************************************************************************
 *                              HOTPLUG
 *************************************************************************)

let hotplug = [ stanza_id "allow-hotplug" . (comment|eol) ]

(************************************************************************
 *                              MAPPING
 *************************************************************************)

let mapping = [ stanza_id "mapping"
               . (comment|empty)+
               . stanza_option
               . (stanza_option|comment|empty)* ]

(************************************************************************
 *                              IFACE
 *************************************************************************)

let iface   = [ stanza_id    "iface"
              . stanza_param "family"
              . stanza_param "method"
              . (comment|empty)+
              . ( stanza_option . (stanza_option|comment|empty)* )? ]

(************************************************************************
 *                              STANZAS
 *************************************************************************)

(* The auto and hotplug stanzas always consist of one line only, while
   iface and mapping can spand along more lines. Comment nodes are
   inserted in the tree as direct children of the root node only when they
   come after an auto or hotplug stanza, otherwise they are considered part
   of a iface or mapping block *)

let stanza_single = (auto|hotplug) . (comment|empty)*
let stanza_multi  = iface|mapping

(************************************************************************
 *                              LENS & FILTER
 *************************************************************************)

   let lns = (comment|empty)* . (stanza_multi | stanza_single)*

   let filter = incl "/etc/network/interfaces"
              . Util.stdexcl

   let xfm = transform lns filter