Fix test_phpvars.aug as per using Util.empty_c_style

module Test_phpvars =

let conf = "<?pHp
/**/
/**
 * Multi line comment
 *
 */

/* One line comment */

// Inline comment
# Bash-style comment
global $version;
global $config_version;
$config_version = '1.4.0';
$theme=array();

$theme[0]['NAME'] = 'Default'; // end-of line comment
$theme[0][\"PATH\"] = SM_PATH . 'themes/default_theme.php';
$theme[0]['XPATH'] = '/some//x/path' ;
define ('MYVAR', ROOT . 'some value'); # end-of line comment
include_once( ROOT . \"/path/to/conf\"	 );
@include SM_PATH . 'config/config_local.php';
 ?>
"

test Phpvars.lns get conf =
  {  }
  { "#mcomment"
    { "1" = "*" }
    { "2" = "* Multi line comment" }
    { "3" = "*" }
  }
  {  }
  { "#mcomment"
    { "1" = "One line comment" }
  }
  {  }
  { "#comment" = "Inline comment" }
  { "#comment" = "Bash-style comment" }
  { "global" = "version" }
  { "global" = "config_version" }
  { "$config_version" = "'1.4.0'" }
  { "$theme" = "array()" }
  {  }
  { "$theme[0]['NAME']" = "'Default'"
    { "#comment" = "end-of line comment"  } }
  { "$theme[0][\"PATH\"]" = "SM_PATH . 'themes/default_theme.php'" }
  { "$theme[0]['XPATH']" = "'/some//x/path'" }
  { "define" = "MYVAR"
    { "value" = "ROOT . 'some value'" }
    { "#comment" = "end-of line comment" } }
  { "include_once" = "ROOT . \"/path/to/conf\"" }
  { "@include" = "SM_PATH . 'config/config_local.php'" }

module Test_phpvars =

let conf = "<?pHp
/**/
/**
 * Multi line comment
 *
 */

/* One line comment */

// Inline comment
# Bash-style comment
global $version;
global $config_version;
$config_version = '1.4.0';
$theme=array();

$theme[0]['NAME'] = 'Default'; // end-of line comment
$theme[0][\"PATH\"] = SM_PATH . 'themes/default_theme.php';
$theme[0]['XPATH'] = '/some//x/path' ;
define ('MYVAR', ROOT . 'some value'); # end-of line comment
include_once( ROOT . \"/path/to/conf\"	 );
@include SM_PATH . 'config/config_local.php';
 ?>
"

test Phpvars.lns get conf =
  { "#mcomment" }
  { "#mcomment"
    { "1" = "*" }
    { "2" = "* Multi line comment" }
    { "3" = "*" }
  }
  {  }
  { "#mcomment"
    { "1" = "One line comment" }
  }
  {  }
  { "#comment" = "Inline comment" }
  { "#comment" = "Bash-style comment" }
  { "global" = "version" }
  { "global" = "config_version" }
  { "$config_version" = "'1.4.0'" }
  { "$theme" = "array()" }
  {  }
  { "$theme[0]['NAME']" = "'Default'"
    { "#comment" = "end-of line comment"  } }
  { "$theme[0][\"PATH\"]" = "SM_PATH . 'themes/default_theme.php'" }
  { "$theme[0]['XPATH']" = "'/some//x/path'" }
  { "define" = "MYVAR"
    { "value" = "ROOT . 'some value'" }
    { "#comment" = "end-of line comment" } }
  { "include_once" = "ROOT . \"/path/to/conf\"" }
  { "@include" = "SM_PATH . 'config/config_local.php'" }


Shellvars: exclude /etc/sysconfig/ip*tables.save files

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

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

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

  let empty   = Util.empty
  let empty_part_re = Util.empty_generic_re . /\n+/
  let eol = del (/[ \t]+|[ \t]*[;\n]/ . empty_part_re*) "\n"
  let semicol_eol = del (/[ \t]*[;\n]/ . empty_part_re*) "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - ("unset" | "export")
  let matching_re = "${!" . key_re . /[\*@]\}/
  let eq = Util.del_str "="

  let eol_for_comment = del /([ \t]*\n)([ \t]*(#[ \t]*)?\n)*/ "\n"
  let comment = Util.comment_generic_seteol /[ \t]*#[ \t]*/ " # " eol_for_comment
  (* comment_eol in shell MUST begin with a space *)
  let comment_eol = Util.comment_generic_seteol /[ \t]+#[ \t]*/ " # " eol_for_comment
  let comment_or_eol = comment_eol | semicol_eol

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

  let char  = /[^`;() '"\t\n]|\\\\"/
  let dquot = /"([^"\\]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]*`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\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 | squot)+ | bquot | dbquot | dollar_assign) 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 | dbquot | dollar_assign | empty_array)

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

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store (key_re | matching_re) . 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" | "shift" | "exit"

  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+ | ("\"" . char+ . "\""))
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . (empty* . comment* . case_entry)*
        . empty* . comment*
        . 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 | 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_norec = empty* . (comment | source | kv | unset | bare_export | builtin | return) *

  let lns = empty* . (comment | 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 "bootloader" .
      sc_excl "hw-uuid" .
      sc_excl "hwconf" .
      sc_excl "ip*tables" .
      sc_excl "ip*tables.save",
      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_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/firewalld/firewalld.conf"
                     . 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/rc.conf.local"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"
                     . incl "/etc/locale.conf"
                     . incl "/etc/vconsole.conf"

  let filter = filter_sysconfig
             . 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 LGPL v2+, like the rest of Augeas.

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

  let empty   = Util.empty
  let empty_part_re = Util.empty_generic_re . /\n+/
  let eol = del (/[ \t]+|[ \t]*[;\n]/ . empty_part_re*) "\n"
  let semicol_eol = del (/[ \t]*[;\n]/ . empty_part_re*) "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/ - ("unset" | "export")
  let matching_re = "${!" . key_re . /[\*@]\}/
  let eq = Util.del_str "="

  let eol_for_comment = del /([ \t]*\n)([ \t]*(#[ \t]*)?\n)*/ "\n"
  let comment = Util.comment_generic_seteol /[ \t]*#[ \t]*/ " # " eol_for_comment
  (* comment_eol in shell MUST begin with a space *)
  let comment_eol = Util.comment_generic_seteol /[ \t]+#[ \t]*/ " # " eol_for_comment
  let comment_or_eol = comment_eol | semicol_eol

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

  let char  = /[^`;() '"\t\n]|\\\\"/
  let dquot = /"([^"\\]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]*`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\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 | squot)+ | bquot | dbquot | dollar_assign) 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 | dbquot | dollar_assign | empty_array)

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

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store (key_re | matching_re) . 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" | "shift" | "exit"

  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+ | ("\"" . char+ . "\""))
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . (empty* . comment* . case_entry)*
        . empty* . comment*
        . 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 | 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_norec = empty* . (comment | source | kv | unset | bare_export | builtin | return) *

  let lns = empty* . (comment | 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 "bootloader" .
      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_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/firewalld/firewalld.conf"
                     . 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/rc.conf.local"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"
                     . incl "/etc/locale.conf"
                     . incl "/etc/vconsole.conf"

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

  let xfm = transform lns filter

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


MySQL: include /etc/my.cnf.d/*.cnf (fix #353)

(* MySQL module for Augeas                    *)
(* Author: Tim Stoop <tim@kumina.nl>          *)
(* Heavily based on php.aug by Raphael Pinson *)
(* <raphink@gmail.com>                        *)
(*                                            *)

module MySQL =
  autoload xfm

(************************************************************************
 * INI File settings
 *************************************************************************)
let comment  = IniFile.comment IniFile.comment_re "#"

let sep      = IniFile.sep IniFile.sep_re IniFile.sep_default

let entry    =
     let bare = Quote.do_dquote_opt_nil (store /[^#;" \t\r\n]+([ \t]+[^#;" \t\r\n]+)*/)
  in let quoted = Quote.do_dquote (store /[^"\r\n]*[#;]+[^"\r\n]*/)
  in [ Util.indent . key IniFile.entry_re . sep . Sep.opt_space . bare . (comment|IniFile.eol) ]
   | [ Util.indent . key IniFile.entry_re . sep . Sep.opt_space . quoted . (comment|IniFile.eol) ]
   | [ Util.indent . key IniFile.entry_re . store // .  (comment|IniFile.eol) ]
   | comment

(************************************************************************
 * sections, led by a "[section]" header
 * We can't use titles as node names here since they could contain "/"
 * We remove #comment from possible keys
 * since it is used as label for comments
 * We also remove / as first character
 * because augeas doesn't like '/' keys (although it is legal in INI Files)
 *************************************************************************)
let title   = IniFile.indented_title_label "target" IniFile.record_label_re
let record  = IniFile.record title entry

let includedir = Build.key_value_line /!include(dir)?/ Sep.space (store Rx.fspath)
               . (comment|IniFile.empty)*

let lns    = (comment|IniFile.empty)* . (record|includedir)*

let filter = (incl "/etc/mysql/my.cnf")
             . (incl "/etc/mysql/conf.d/*.cnf")
             . (incl "/etc/my.cnf")
             . (incl "/etc/my.cnf.d/*.cnf")

let xfm = transform lns filter

(* MySQL module for Augeas                    *)
(* Author: Tim Stoop <tim@kumina.nl>          *)
(* Heavily based on php.aug by Raphael Pinson *)
(* <raphink@gmail.com>                        *)
(*                                            *)

module MySQL =
  autoload xfm

(************************************************************************
 * INI File settings
 *************************************************************************)
let comment  = IniFile.comment IniFile.comment_re "#"

let sep      = IniFile.sep IniFile.sep_re IniFile.sep_default

let entry    =
     let bare = Quote.do_dquote_opt_nil (store /[^#;" \t\r\n]+([ \t]+[^#;" \t\r\n]+)*/)
  in let quoted = Quote.do_dquote (store /[^"\r\n]*[#;]+[^"\r\n]*/)
  in [ Util.indent . key IniFile.entry_re . sep . Sep.opt_space . bare . (comment|IniFile.eol) ]
   | [ Util.indent . key IniFile.entry_re . sep . Sep.opt_space . quoted . (comment|IniFile.eol) ]
   | [ Util.indent . key IniFile.entry_re . store // .  (comment|IniFile.eol) ]
   | comment

(************************************************************************
 * sections, led by a "[section]" header
 * We can't use titles as node names here since they could contain "/"
 * We remove #comment from possible keys
 * since it is used as label for comments
 * We also remove / as first character
 * because augeas doesn't like '/' keys (although it is legal in INI Files)
 *************************************************************************)
let title   = IniFile.indented_title_label "target" IniFile.record_label_re
let record  = IniFile.record title entry

let includedir = Build.key_value_line /!include(dir)?/ Sep.space (store Rx.fspath)
               . (comment|IniFile.empty)*

let lns    = (comment|IniFile.empty)* . (record|includedir)*

let filter = (incl "/etc/mysql/my.cnf")
             . (incl "/etc/mysql/conf.d/*.cnf")
             . (incl "/etc/my.cnf")

let xfm = transform lns filter


Nsswitch: add extra databases used on Solaris

(*
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"
                  | "auth_attr"
                  | "automount"
                  | "bootparams"
                  | "ethers"
                  | "group"
                  | "group_compat"
                  | "hosts"
                  | "ipnodes"
                  | "netgroup"
                  | "netmasks"
                  | "networks"
                  | "passwd"
                  | "passwd_compat"
                  | "printers"
                  | "prof_attr"
                  | "project"
                  | "protocols"
                  | "publickey"
                  | "rpc"
                  | "sendmailvars"
                  | "services"
                  | "shadow"
                  | "shadow_compat"
                  | "sudoers"
                  | "tnrhtp"
                  | "tnrhdb"
    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"
                  | "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


* lenses/tests/test_fstab.aug: test that we parse SELinux context correctly

module Test_fstab =

  let simple = "/dev/vg00/lv00\t /\t ext3\t    defaults        1 1\n"

  let simple_tree =
    { "1"
        { "spec" = "/dev/vg00/lv00" }
        { "file" = "/" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" }
        { "dump" = "1" }
        { "passno" = "1" } }

  let trailing_ws = "/dev/vg00/lv00\t /\t ext3\t    defaults        1 1  \t\n"

  let gen_no_passno(passno:string) =
    "LABEL=/boot\t /boot\t ext3\t    defaults        1" . passno . "  \t\n"
  let no_passno = gen_no_passno ""

  let no_passno_tree =
    { "1"
        { "spec" = "LABEL=/boot" }
        { "file" = "/boot" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" }
        { "dump" = "1" } }

  let no_dump = "/dev/vg00/lv00\t /\t ext3\t    defaults\n"

  let no_dump_tree =
    { "1"
        { "spec" = "/dev/vg00/lv00" }
        { "file" = "/" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" } }

  let multi_opts = "devpts\t /dev/pts\t devpts  gid=5,mode=620,fscontext=system_u:object_r:removable_t  0 0\n"

  let multi_opts_tree =
    { "1"
        { "spec" = "devpts" }
        { "file" = "/dev/pts" }
        { "vfstype" = "devpts" }
        { "opt" = "gid"
            { "value" = "5" } }
        { "opt" = "mode"
            { "value" = "620" } }
        { "opt" = "fscontext"
            { "value" = "system_u:object_r:removable_t" } }
        { "dump" = "0" }
        { "passno" = "0" } }

  test Fstab.lns get simple = simple_tree

  test Fstab.lns get trailing_ws = simple_tree

  test Fstab.lns get no_passno = no_passno_tree

  test Fstab.lns put no_passno after set "/1/passno" "1" = gen_no_passno " 1"

  test Fstab.lns get no_dump = no_dump_tree

  test Fstab.lns get multi_opts = multi_opts_tree

  test Fstab.lns get "/dev/hdc        /media/cdrom0   udf,iso9660 user,noauto\t0\t0\n" =
    { "1"
        { "spec" = "/dev/hdc" }
        { "file" = "/media/cdrom0" }
        { "vfstype" = "udf" }
        { "vfstype" = "iso9660" }
        { "opt" = "user" }
        { "opt" = "noauto" }
        { "dump" = "0" }
        { "passno" = "0" } }

  (* Allow # in the spec *)
  test Fstab.lns get "sshfs#jon@10.0.0.2:/home    /media/server    fuse    uid=1000,gid=100,port=1022 0 0\n" =
  { "1"
    { "spec" = "sshfs#jon@10.0.0.2:/home" }
    { "file" = "/media/server" }
    { "vfstype" = "fuse" }
    { "opt" = "uid"
         { "value" = "1000" } }
    { "opt" = "gid"
         { "value" = "100" } }
    { "opt" = "port"
         { "value" = "1022" } }
    { "dump" = "0" }
    { "passno" = "0" } }

  (* Bug #191 *)
  test Fstab.lns get "tmpfs /dev/shm tmpfs rw,rootcontext=\"system_u:object_r:tmpfs_t:s0\" 0 0\n" =
  { "1"
    { "spec" = "tmpfs" }
    { "file" = "/dev/shm" }
    { "vfstype" = "tmpfs" }
    { "opt" = "rw" }
    { "opt" = "rootcontext"
      { "value" = "\"system_u:object_r:tmpfs_t:s0\"" } }
    { "dump" = "0" }
    { "passno" = "0" } }

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

module Test_fstab =

  let simple = "/dev/vg00/lv00\t /\t ext3\t    defaults        1 1\n"

  let simple_tree =
    { "1"
        { "spec" = "/dev/vg00/lv00" }
        { "file" = "/" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" }
        { "dump" = "1" }
        { "passno" = "1" } }

  let trailing_ws = "/dev/vg00/lv00\t /\t ext3\t    defaults        1 1  \t\n"

  let gen_no_passno(passno:string) =
    "LABEL=/boot\t /boot\t ext3\t    defaults        1" . passno . "  \t\n"
  let no_passno = gen_no_passno ""

  let no_passno_tree =
    { "1"
        { "spec" = "LABEL=/boot" }
        { "file" = "/boot" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" }
        { "dump" = "1" } }

  let no_dump = "/dev/vg00/lv00\t /\t ext3\t    defaults\n"

  let no_dump_tree =
    { "1"
        { "spec" = "/dev/vg00/lv00" }
        { "file" = "/" }
        { "vfstype" = "ext3" }
        { "opt" = "defaults" } }

  let multi_opts = "devpts\t /dev/pts\t devpts  gid=5,mode=620,fscontext=system_u:object_r:removable_t  0 0\n"

  let multi_opts_tree =
    { "1"
        { "spec" = "devpts" }
        { "file" = "/dev/pts" }
        { "vfstype" = "devpts" }
        { "opt" = "gid"
            { "value" = "5" } }
        { "opt" = "mode"
            { "value" = "620" } }
        { "opt" = "fscontext"
            { "value" = "system_u:object_r:removable_t" } }
        { "dump" = "0" }
        { "passno" = "0" } }

  test Fstab.lns get simple = simple_tree

  test Fstab.lns get trailing_ws = simple_tree

  test Fstab.lns get no_passno = no_passno_tree

  test Fstab.lns put no_passno after set "/1/passno" "1" = gen_no_passno " 1"

  test Fstab.lns get no_dump = no_dump_tree

  test Fstab.lns get multi_opts = multi_opts_tree

  test Fstab.lns get "/dev/hdc        /media/cdrom0   udf,iso9660 user,noauto\t0\t0\n" =
    { "1"
        { "spec" = "/dev/hdc" }
        { "file" = "/media/cdrom0" }
        { "vfstype" = "udf" }
        { "vfstype" = "iso9660" }
        { "opt" = "user" }
        { "opt" = "noauto" }
        { "dump" = "0" }
        { "passno" = "0" } }

  (* Allow # in the spec *)
  test Fstab.lns get "sshfs#jon@10.0.0.2:/home    /media/server    fuse    uid=1000,gid=100,port=1022 0 0\n" =
  { "1"
    { "spec" = "sshfs#jon@10.0.0.2:/home" }
    { "file" = "/media/server" }
    { "vfstype" = "fuse" }
    { "opt" = "uid"
         { "value" = "1000" } }
    { "opt" = "gid"
         { "value" = "100" } }
    { "opt" = "port"
         { "value" = "1022" } }
    { "dump" = "0" }
    { "passno" = "0" } }

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


vmware_config: use square lens to manage quotes

(*
Module: VWware_Config
  Parses /etc/vmware/config

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

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/vmware/config. See <filter>.

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

autoload xfm

(* View: entry *)
let entry =
     let quote = del /"?/ "\""
  in let body = store /[^\n\t "]([^\n"]*[^\n\t "])?/
  in let sto = square quote body quote
  in Build.key_value_line Rx.word Sep.space_equal sto

(* View: lns *)
let lns = (Util.empty | Util.comment | entry)*

(* Variable: filter *)
let filter = incl "/etc/vmware/config"

let xfm = transform lns filter

(*
Module: VWware_Config
  Parses /etc/vmware/config

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

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/vmware/config. See <filter>.

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

autoload xfm

(* View: entry *)
let entry =
  let sto = del /"?/ "\"" . store /[^\n\t "]([^\n"]*[^\n\t "])?/ . del /"?/ "\""
  in Build.key_value_line Rx.word Sep.space_equal sto

(* View: lns *)
let lns = (Util.empty | Util.comment | entry)*

(* Variable: filter *)
let filter = incl "/etc/vmware/config"

let xfm = transform lns filter


Use Build.opt_list in sudoers.aug

Use Build.opt_list in sudoers.aug

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

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

For example, recursive definitions such as

     > Cmnd_Spec_List ::= Cmnd_Spec |
     >                    Cmnd_Spec ',' Cmnd_Spec_List

are replaced by

  >   let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

since Augeas cannot deal with recursive definitions.
The definitions from `man sudoers` are put as commentaries for reference
throughout the file. More information can be found in the manual.

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

About: Lens Usage
  Sample usage of this lens in augtool

    * Set first Defaults to apply to the "LOCALNET" network alias
      > set /files/etc/sudoers/Defaults[1]/type "@LOCALNET"
    * List all user specifications applying explicitely to the "admin" Unix group
      > match /files/etc/sudoers/spec/user "%admin"
    * Remove the full 3rd user specification
      > rm /files/etc/sudoers/spec[3]

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

module Sudoers =
  autoload xfm

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

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = Util.eol

(* Variable: indent *)
let indent    = Util.indent

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc  = Sep.space

(* Variable: sep_cont *)
let sep_cont = Sep.cl_or_space

(* Variable: sep_cont_opt *)
let sep_cont_opt = Sep.cl_or_opt_space

(* Variable: sep_cont_opt_build *)
let sep_cont_opt_build (sep:string) =
   del (Rx.cl_or_opt_space . sep . Rx.cl_or_opt_space) (" " . sep . " ")

(* Variable: sep_com *)
let sep_com = sep_cont_opt_build ","

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt_build "="

(* Variable: sep_col *)
let sep_col  = sep_cont_opt_build ":"

(* Variable: sep_dquote *)
let sep_dquote   = Util.del_str "\""

(* Group: Stores *)

(* Variable: sto_to_com_cmnd
sto_to_com_cmnd does not begin or end with a space *)
let sto_to_com_cmnd = store /([^,=:#() \t\n\\]([^,=:#()\n\\]|\\\\[=:,\\])*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

(* Variable: sto_to_com

There could be a \ in the middle of a command *)
let sto_to_com      = store /([^,=:#() \t\n\\][^,=:#()\n]*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

(* Variable: sto_to_com_host *)
let sto_to_com_host = store /[^,=:#() \t\n\\]+/

(* Variable: sto_to_com_user
Escaped spaces and NIS domains and allowed*)
let sto_to_com_user =
      let nis_re = /([A-Z]([-A-Z0-9]|(\\\\[ \t]))*+\\\\\\\\)/
   in let user_re = /[%+@a-z]([-a-z0-9]|(\\\\[ \t]))*/
   in store (nis_re? . user_re)

(* Variable: sto_to_com_col *)
let sto_to_com_col      = store /[^",=#() \t\n\\]+/ (* " relax emacs *)

(* Variable: sto_to_eq *)
let sto_to_eq  = store /[^,=:#() \t\n\\]+/

(* Variable: sto_to_spc *)
let sto_to_spc = store /[^", \t\n\\]+|"[^", \t\n\\]+"/

(* Variable: sto_to_spc_no_dquote *)
let sto_to_spc_no_dquote = store /[^",# \t\n\\]+/ (* " relax emacs *)

(* Variable: sto_integer *)
let sto_integer = store /[0-9]+/

(* Group: Comments and empty lines *)

(* View: comment
Map comments in "#comment" nodes *)
let comment =
  let sto_to_eol = store (/([^ \t\n].*[^ \t\n]|[^ \t\n])/ - /include(dir)?.*/) in
  [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

(* View: comment_eol
Requires a space before the # *)
let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "

(* View: comment_or_eol
A <comment_eol> or <eol> *)
let comment_or_eol = comment_eol | (del /([ \t]+#\n|[ \t]*\n)/ "\n")

(* View: empty
Map empty lines *)
let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(* View: includedir *)
let includedir =
  [ key /#include(dir)?/ . Sep.space . store Rx.fspath . eol ]

(************************************************************************
 * Group:                                    ALIASES
 *************************************************************************)

(************************************************************************
 * View: alias_field
 *   Generic alias field to gather all Alias definitions
 *
 *   Definition:
 *     > User_Alias ::= NAME '=' User_List
 *     > Runas_Alias ::= NAME '=' Runas_List
 *     > Host_Alias ::= NAME '=' Host_List
 *     > Cmnd_Alias ::= NAME '=' Cmnd_List
 *
 *   Parameters:
 *     kw:string - the label string
 *     sto:lens  - the store lens
 *************************************************************************)
let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

(* View: alias_list
     List of <alias_fields>, separated by commas *)
let alias_list  (kw:string) (sto:lens) =
  Build.opt_list (alias_field kw sto) sep_com

(************************************************************************
 * View: alias_name
 *   Name of an <alias_entry_single>
 *
 *   Definition:
 *     > NAME ::= [A-Z]([A-Z][0-9]_)*
 *************************************************************************)
let alias_name
    = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

(************************************************************************
 * View: alias_entry_single
 *   Single <alias_entry>, named using <alias_name> and listing <alias_list>
 *
 *   Definition:
 *     > Alias_Type NAME = item1, item2, ...
 *
 *   Parameters:
 *     field:string - the field name, passed to <alias_list>
 *     sto:lens     - the store lens, passed to <alias_list>
 *************************************************************************)
let alias_entry_single (field:string) (sto:lens)
    = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

(************************************************************************
 * View: alias_entry
 *   Alias entry, a list of comma-separated <alias_entry_single> fields
 *
 *   Definition:
 *     > Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
 *
 *   Parameters:
 *     kw:string    - the alias keyword string
 *     field:string - the field name, passed to <alias_entry_single>
 *     sto:lens     - the store lens, passed to <alias_entry_single>
 *************************************************************************)
let alias_entry (kw:string) (field:string) (sto:lens)
    = [ indent . key kw . sep_cont . alias_entry_single field sto
          . ( sep_col . alias_entry_single field sto )* . comment_or_eol ]

(* TODO: go further in user definitions *)
(* View: user_alias
     User_Alias, see <alias_field> *)
let user_alias  = alias_entry "User_Alias" "user" sto_to_com
(* View: runas_alias
     Run_Alias, see <alias_field> *)
let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com
(* View: host_alias
     Host_Alias, see <alias_field> *)
let host_alias  = alias_entry "Host_Alias" "host" sto_to_com
(* View: cmnd_alias
     Cmnd_Alias, see <alias_field> *)
let cmnd_alias  = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

(************************************************************************
 * View: alias
 *   Every kind of Alias entry,
 *     see <user_alias>, <runas_alias>, <host_alias> and <cmnd_alias>
 *
 *   Definition:
 *     > Alias ::= 'User_Alias'  User_Alias (':' User_Alias)* |
 *     >           'Runas_Alias' Runas_Alias (':' Runas_Alias)* |
 *     >           'Host_Alias'  Host_Alias (':' Host_Alias)* |
 *     >           'Cmnd_Alias'  Cmnd_Alias (':' Cmnd_Alias)*
 *************************************************************************)
let alias = user_alias | runas_alias | host_alias | cmnd_alias
(************************************************************************
 * Group:                          DEFAULTS
 *************************************************************************)

(************************************************************************
 * View: default_type
 *   Type definition for <defaults>
 *
 *   Definition:
 *     > Default_Type ::= 'Defaults' |
 *     >                  'Defaults' '@' Host_List |
 *     >                  'Defaults' ':' User_List |
 *     >                  'Defaults' '>' Runas_List
 *************************************************************************)
let default_type     =
  let value = store /[@:>][^ \t\n\\]+/ in
  [ label "type" . value ]

(************************************************************************
 * View: del_negate
 *   Delete an even number of '!' signs
 *************************************************************************)
let del_negate = del /(!!)*/ ""

(************************************************************************
 * View: negate_node
 *   Negation of boolean values for <defaults>. Accept one optional '!'
 *   and produce a 'negate' node if there is one.
 *************************************************************************)
let negate_node = [ del "!" "!" . label "negate" ]

let negate_or_value (key:lens) (value:lens) =
  [ del_negate . (negate_node . key | key . value) ]

(************************************************************************
 * View: parameter_flag
 *   A flag parameter for <defaults>
 *
 *   Flags are implicitly boolean and can be turned off via the '!'  operator.
 *   Some integer, string and list parameters may also be used in a boolean
 *     context to disable them.
 *************************************************************************)
let parameter_flag_kw    = "always_set_home" | "authenticate" | "env_editor"
                         | "env_reset" | "fqdn" | "ignore_dot"
                         | "ignore_local_sudoers" | "insults" | "log_host"
                         | "log_year" | "long_otp_prompt" | "mail_always"
                         | "mail_badpass" | "mail_no_host" | "mail_no_perms"
                         | "mail_no_user" | "noexec" | "path_info"
                         | "passprompt_override" | "preserve_groups"
                         | "requiretty" | "root_sudo" | "rootpw" | "runaspw"
                         | "set_home" | "set_logname" | "setenv"
                         | "shell_noargs" | "stay_setuid" | "targetpw"
                         | "tty_tickets" | "visiblepw" | "closefrom_override"
                         | "closefrom_override" | "compress_io" | "fast_glob"
                         | "log_input" | "log_output" | "pwfeedback"
                         | "umask_override" | "use_pty"

let parameter_flag       = [ del_negate . negate_node?
                               . key parameter_flag_kw ]

(************************************************************************
 * View: parameter_integer
 *   An integer parameter for <defaults>
 *************************************************************************)
let parameter_integer_nobool_kw = "passwd_tries"

let parameter_integer_nobool    = [ key parameter_integer_nobool_kw . sep_eq
                                      . del /"?/ "" . sto_integer
                                      . del /"?/ "" ]

let parameter_integer_bool_kw   = "loglinelen" | "passwd_timeout"
                                | "timestamp_timeout" | "umask"

let parameter_integer_bool      =
  negate_or_value
    (key parameter_integer_bool_kw)
    (sep_eq . del /"?/ "" . sto_integer . del /"?/ "")

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

(************************************************************************
 * View: parameter_string
 *   A string parameter for <defaults>
 *
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub"
                               | "noexec_file" | "passprompt" | "runas_default"
                               | "syslog_badpri" | "syslog_goodpri"
                               | "timestampdir" | "timestampowner" | "secure_path"

let parameter_string_nobool    = [ key parameter_string_nobool_kw . sep_eq
                                     . del /"?/ "" . sto_to_com_col
                                     . del /"?/ "" ]

let parameter_string_bool_kw   = "exempt_group" | "lecture" | "lecture_file"
                               | "listpw" | "logfile" | "mailerflags"
                               | "mailerpath" | "mailto" | "exempt_group"
                               | "syslog" | "verifypw" | "logfile"
                               | "mailerflags" | "mailerpath" | "mailto"
                               | "syslog" | "verifypw"

let parameter_string_bool      =
  negate_or_value
    (key parameter_string_bool_kw)
    (sep_eq . sto_to_com_col)

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

(************************************************************************
 * View: parameter_lists
 *   A single list parameter for <defaults>
 *
 *   All lists can be used in a boolean context
 *   The argument may be a double-quoted, space-separated list or a single
 *      value without double-quotes.
 *   The list can be replaced, added to, deleted from, or disabled
 *      by using the =, +=, -=, and ! operators respectively.
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_lists_kw           = "env_check" | "env_delete" | "env_keep"
let parameter_lists_value        = [ label "var" . sto_to_spc_no_dquote ]
let parameter_lists_value_dquote = [ label "var"
                                     . del /"?/ "" . sto_to_spc_no_dquote
                                     . del /"?/ "" ]

let parameter_lists_values = parameter_lists_value_dquote
                           | ( sep_dquote . parameter_lists_value
                               . ( sep_cont . parameter_lists_value )+
                               . sep_dquote )

let parameter_lists_sep    = sep_cont_opt
                             . ( [ del "+" "+" . label "append" ]
                               | [ del "-" "-" . label "remove" ] )?
                             . del "=" "=" . sep_cont_opt

let parameter_lists        =
  negate_or_value
    (key parameter_lists_kw)
    (parameter_lists_sep . parameter_lists_values)

(************************************************************************
 * View: parameter
 *   A single parameter for <defaults>
 *
 *   Definition:
 *     > Parameter ::= Parameter '=' Value |
 *     >               Parameter '+=' Value |
 *     >               Parameter '-=' Value |
 *     >               '!'* Parameter
 *
 *     Parameters may be flags, integer values, strings, or lists.
 *
 *************************************************************************)
let parameter        = parameter_flag | parameter_integer
                     | parameter_string | parameter_lists

(************************************************************************
 * View: parameter_list
 *   A list of comma-separated <parameters> for <defaults>
 *
 *   Definition:
 *     > Parameter_List ::= Parameter |
 *     >                    Parameter ',' Parameter_List
 *************************************************************************)
let parameter_list   = parameter . ( sep_com . parameter )*

(************************************************************************
 * View: defaults
 *   A Defaults entry
 *
 *   Definition:
 *     > Default_Entry ::= Default_Type Parameter_List
 *************************************************************************)
let defaults = [ indent . key "Defaults" . default_type? . sep_cont
                   . parameter_list . comment_or_eol ]

(************************************************************************
 * Group:                     USER SPECIFICATION
 *************************************************************************)

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list> for listing
 *   users and/or groups used to run a command
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')' |
 *     >                '(:' Runas_List ')' |
 *     >                '(' Runas_List ':' Runas_List ')'
 *************************************************************************)
let runas_spec_user       = alias_list "runas_user" sto_to_com
let runas_spec_group      = Util.del_str ":" . indent
                            . alias_list "runas_group" sto_to_com

let runas_spec_usergroup  = runas_spec_user . indent . runas_spec_group

let runas_spec = Util.del_str "("
                 . (runas_spec_user
                    | runas_spec_group
                    | runas_spec_usergroup )
                 . Util.del_str ")" . sep_cont_opt

(************************************************************************
 * View: tag_spec
 *   Tag specification for <spec>
 *
 *   Definition:
 *     > Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
 *     >              'SETENV:' | 'NOSETENV:')
 *************************************************************************)
let tag_spec   =
  [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

(************************************************************************
 * View: cmnd_spec
 *   Command specification for <spec>,
 *     with optional <runas_spec> and any amount of <tag_specs>
 *
 *   Definition:
 *     > Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd
 *************************************************************************)
let cmnd_spec  =
  [ label "command" .  runas_spec? . tag_spec* . sto_to_com_cmnd ]

(************************************************************************
 * View: cmnd_spec_list
 *   A list of comma-separated <cmnd_specs>
 *
 *   Definition:
 *     > Cmnd_Spec_List ::= Cmnd_Spec |
 *     >                    Cmnd_Spec ',' Cmnd_Spec_List
 *************************************************************************)
let cmnd_spec_list = Build.opt_list cmnd_spec sep_com

(************************************************************************
 * View: spec_list
 *   Group of hosts with <cmnd_spec_list>
 *************************************************************************)
let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host
                    . sep_eq . cmnd_spec_list ]

(************************************************************************
 * View: spec
 *   A user specification, listing colon-separated <spec_lists>
 *
 *   Definition:
 *     > User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
 *     >               (':' Host_List '=' Cmnd_Spec_List)*
 *************************************************************************)
let spec = [ label "spec" . indent
               . alias_list "user" sto_to_com_user . sep_cont
               . Build.opt_list spec_list sep_col
               . comment_or_eol ]

(************************************************************************
 * Group:                        LENS & FILTER
 *************************************************************************)

(* View: lns
     The sudoers lens, any amount of
       * <empty> lines
       * <comments>
       * <includedirs>
       * <aliases>
       * <defaults>
       * <specs>
*)
let lns = ( empty | comment | includedir | alias | defaults | spec  )*

(* View: filter *)
let filter = (incl "/etc/sudoers")
    . (incl "/usr/local/etc/sudoers")
    . (incl "/etc/sudoers.d/*")
    . (incl "/usr/local/etc/sudoers.d/*")
    . (incl "/opt/csw/etc/sudoers")
    . (incl "/etc/opt/csw/sudoers")
    . Util.stdexcl

let xfm = transform lns filter

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

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

For example, recursive definitions such as

     > Cmnd_Spec_List ::= Cmnd_Spec |
     >                    Cmnd_Spec ',' Cmnd_Spec_List

are replaced by

  >   let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

since Augeas cannot deal with recursive definitions.
The definitions from `man sudoers` are put as commentaries for reference
throughout the file. More information can be found in the manual.

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

About: Lens Usage
  Sample usage of this lens in augtool

    * Set first Defaults to apply to the "LOCALNET" network alias
      > set /files/etc/sudoers/Defaults[1]/type "@LOCALNET"
    * List all user specifications applying explicitely to the "admin" Unix group
      > match /files/etc/sudoers/spec/user "%admin"
    * Remove the full 3rd user specification
      > rm /files/etc/sudoers/spec[3]

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

module Sudoers =
  autoload xfm

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

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = Util.eol

(* Variable: indent *)
let indent    = Util.indent

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc  = Sep.space

(* Variable: sep_cont *)
let sep_cont = Sep.cl_or_space

(* Variable: sep_cont_opt *)
let sep_cont_opt = Sep.cl_or_opt_space

(* Variable: sep_cont_opt_build *)
let sep_cont_opt_build (sep:string) =
   del (Rx.cl_or_opt_space . sep . Rx.cl_or_opt_space) (" " . sep . " ")

(* Variable: sep_com *)
let sep_com = sep_cont_opt_build ","

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt_build "="

(* Variable: sep_col *)
let sep_col  = sep_cont_opt_build ":"

(* Variable: sep_dquote *)
let sep_dquote   = Util.del_str "\""

(* Group: Stores *)

(* Variable: sto_to_com_cmnd
sto_to_com_cmnd does not begin or end with a space *)
let sto_to_com_cmnd = store /([^,=:#() \t\n\\]([^,=:#()\n\\]|\\\\[=:,\\])*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

(* Variable: sto_to_com

There could be a \ in the middle of a command *)
let sto_to_com      = store /([^,=:#() \t\n\\][^,=:#()\n]*[^,=:#() \t\n\\])|[^,=:#() \t\n\\]/

(* Variable: sto_to_com_host *)
let sto_to_com_host = store /[^,=:#() \t\n\\]+/

(* Variable: sto_to_com_user
Escaped spaces and NIS domains and allowed*)
let sto_to_com_user =
      let nis_re = /([A-Z]([-A-Z0-9]|(\\\\[ \t]))*+\\\\\\\\)/
   in let user_re = /[%+@a-z]([-a-z0-9]|(\\\\[ \t]))*/
   in store (nis_re? . user_re)

(* Variable: sto_to_com_col *)
let sto_to_com_col      = store /[^",=#() \t\n\\]+/ (* " relax emacs *)

(* Variable: sto_to_eq *)
let sto_to_eq  = store /[^,=:#() \t\n\\]+/

(* Variable: sto_to_spc *)
let sto_to_spc = store /[^", \t\n\\]+|"[^", \t\n\\]+"/

(* Variable: sto_to_spc_no_dquote *)
let sto_to_spc_no_dquote = store /[^",# \t\n\\]+/ (* " relax emacs *)

(* Variable: sto_integer *)
let sto_integer = store /[0-9]+/

(* Group: Comments and empty lines *)

(* View: comment
Map comments in "#comment" nodes *)
let comment =
  let sto_to_eol = store (/([^ \t\n].*[^ \t\n]|[^ \t\n])/ - /include(dir)?.*/) in
  [ label "#comment" . del /[ \t]*#[ \t]*/ "# " . sto_to_eol . eol ]

(* View: comment_eol
Requires a space before the # *)
let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "

(* View: comment_or_eol
A <comment_eol> or <eol> *)
let comment_or_eol = comment_eol | (del /([ \t]+#\n|[ \t]*\n)/ "\n")

(* View: empty
Map empty lines *)
let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(* View: includedir *)
let includedir =
  [ key /#include(dir)?/ . Sep.space . store Rx.fspath . eol ]

(************************************************************************
 * Group:                                    ALIASES
 *************************************************************************)

(************************************************************************
 * View: alias_field
 *   Generic alias field to gather all Alias definitions
 *
 *   Definition:
 *     > User_Alias ::= NAME '=' User_List
 *     > Runas_Alias ::= NAME '=' Runas_List
 *     > Host_Alias ::= NAME '=' Host_List
 *     > Cmnd_Alias ::= NAME '=' Cmnd_List
 *
 *   Parameters:
 *     kw:string - the label string
 *     sto:lens  - the store lens
 *************************************************************************)
let alias_field (kw:string) (sto:lens) = [ label kw . sto ]

(* View: alias_list
     List of <alias_fields>, separated by commas *)
let alias_list  (kw:string) (sto:lens) =
  Build.opt_list (alias_field kw sto) sep_com

(************************************************************************
 * View: alias_name
 *   Name of an <alias_entry_single>
 *
 *   Definition:
 *     > NAME ::= [A-Z]([A-Z][0-9]_)*
 *************************************************************************)
let alias_name
    = [ label "name" . store /[A-Z][A-Z0-9_]*/ ]

(************************************************************************
 * View: alias_entry_single
 *   Single <alias_entry>, named using <alias_name> and listing <alias_list>
 *
 *   Definition:
 *     > Alias_Type NAME = item1, item2, ...
 *
 *   Parameters:
 *     field:string - the field name, passed to <alias_list>
 *     sto:lens     - the store lens, passed to <alias_list>
 *************************************************************************)
let alias_entry_single (field:string) (sto:lens)
    = [ label "alias" . alias_name . sep_eq . alias_list field sto ]

(************************************************************************
 * View: alias_entry
 *   Alias entry, a list of comma-separated <alias_entry_single> fields
 *
 *   Definition:
 *     > Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
 *
 *   Parameters:
 *     kw:string    - the alias keyword string
 *     field:string - the field name, passed to <alias_entry_single>
 *     sto:lens     - the store lens, passed to <alias_entry_single>
 *************************************************************************)
let alias_entry (kw:string) (field:string) (sto:lens)
    = [ indent . key kw . sep_cont . alias_entry_single field sto
          . ( sep_col . alias_entry_single field sto )* . comment_or_eol ]

(* TODO: go further in user definitions *)
(* View: user_alias
     User_Alias, see <alias_field> *)
let user_alias  = alias_entry "User_Alias" "user" sto_to_com
(* View: runas_alias
     Run_Alias, see <alias_field> *)
let runas_alias = alias_entry "Runas_Alias" "runas_user" sto_to_com
(* View: host_alias
     Host_Alias, see <alias_field> *)
let host_alias  = alias_entry "Host_Alias" "host" sto_to_com
(* View: cmnd_alias
     Cmnd_Alias, see <alias_field> *)
let cmnd_alias  = alias_entry "Cmnd_Alias" "command" sto_to_com_cmnd

(************************************************************************
 * View: alias
 *   Every kind of Alias entry,
 *     see <user_alias>, <runas_alias>, <host_alias> and <cmnd_alias>
 *
 *   Definition:
 *     > Alias ::= 'User_Alias'  User_Alias (':' User_Alias)* |
 *     >           'Runas_Alias' Runas_Alias (':' Runas_Alias)* |
 *     >           'Host_Alias'  Host_Alias (':' Host_Alias)* |
 *     >           'Cmnd_Alias'  Cmnd_Alias (':' Cmnd_Alias)*
 *************************************************************************)
let alias = user_alias | runas_alias | host_alias | cmnd_alias
(************************************************************************
 * Group:                          DEFAULTS
 *************************************************************************)

(************************************************************************
 * View: default_type
 *   Type definition for <defaults>
 *
 *   Definition:
 *     > Default_Type ::= 'Defaults' |
 *     >                  'Defaults' '@' Host_List |
 *     >                  'Defaults' ':' User_List |
 *     >                  'Defaults' '>' Runas_List
 *************************************************************************)
let default_type     =
  let value = store /[@:>][^ \t\n\\]+/ in
  [ label "type" . value ]

(************************************************************************
 * View: del_negate
 *   Delete an even number of '!' signs
 *************************************************************************)
let del_negate = del /(!!)*/ ""

(************************************************************************
 * View: negate_node
 *   Negation of boolean values for <defaults>. Accept one optional '!'
 *   and produce a 'negate' node if there is one.
 *************************************************************************)
let negate_node = [ del "!" "!" . label "negate" ]

let negate_or_value (key:lens) (value:lens) =
  [ del_negate . (negate_node . key | key . value) ]

(************************************************************************
 * View: parameter_flag
 *   A flag parameter for <defaults>
 *
 *   Flags are implicitly boolean and can be turned off via the '!'  operator.
 *   Some integer, string and list parameters may also be used in a boolean
 *     context to disable them.
 *************************************************************************)
let parameter_flag_kw    = "always_set_home" | "authenticate" | "env_editor"
                         | "env_reset" | "fqdn" | "ignore_dot"
                         | "ignore_local_sudoers" | "insults" | "log_host"
                         | "log_year" | "long_otp_prompt" | "mail_always"
                         | "mail_badpass" | "mail_no_host" | "mail_no_perms"
                         | "mail_no_user" | "noexec" | "path_info"
                         | "passprompt_override" | "preserve_groups"
                         | "requiretty" | "root_sudo" | "rootpw" | "runaspw"
                         | "set_home" | "set_logname" | "setenv"
                         | "shell_noargs" | "stay_setuid" | "targetpw"
                         | "tty_tickets" | "visiblepw" | "closefrom_override"
                         | "closefrom_override" | "compress_io" | "fast_glob"
                         | "log_input" | "log_output" | "pwfeedback"
                         | "umask_override" | "use_pty"

let parameter_flag       = [ del_negate . negate_node?
                               . key parameter_flag_kw ]

(************************************************************************
 * View: parameter_integer
 *   An integer parameter for <defaults>
 *************************************************************************)
let parameter_integer_nobool_kw = "passwd_tries"

let parameter_integer_nobool    = [ key parameter_integer_nobool_kw . sep_eq
                                      . del /"?/ "" . sto_integer
                                      . del /"?/ "" ]

let parameter_integer_bool_kw   = "loglinelen" | "passwd_timeout"
                                | "timestamp_timeout" | "umask"

let parameter_integer_bool      =
  negate_or_value
    (key parameter_integer_bool_kw)
    (sep_eq . del /"?/ "" . sto_integer . del /"?/ "")

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

(************************************************************************
 * View: parameter_string
 *   A string parameter for <defaults>
 *
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_string_nobool_kw = "badpass_message" | "editor" | "mailsub"
                               | "noexec_file" | "passprompt" | "runas_default"
                               | "syslog_badpri" | "syslog_goodpri"
                               | "timestampdir" | "timestampowner" | "secure_path"

let parameter_string_nobool    = [ key parameter_string_nobool_kw . sep_eq
                                     . del /"?/ "" . sto_to_com_col
                                     . del /"?/ "" ]

let parameter_string_bool_kw   = "exempt_group" | "lecture" | "lecture_file"
                               | "listpw" | "logfile" | "mailerflags"
                               | "mailerpath" | "mailto" | "exempt_group"
                               | "syslog" | "verifypw" | "logfile"
                               | "mailerflags" | "mailerpath" | "mailto"
                               | "syslog" | "verifypw"

let parameter_string_bool      =
  negate_or_value
    (key parameter_string_bool_kw)
    (sep_eq . sto_to_com_col)

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

(************************************************************************
 * View: parameter_lists
 *   A single list parameter for <defaults>
 *
 *   All lists can be used in a boolean context
 *   The argument may be a double-quoted, space-separated list or a single
 *      value without double-quotes.
 *   The list can be replaced, added to, deleted from, or disabled
 *      by using the =, +=, -=, and ! operators respectively.
 *   An odd number of '!' operators negate the value of the item;
 *      an even number just cancel each other out.
 *************************************************************************)
let parameter_lists_kw           = "env_check" | "env_delete" | "env_keep"
let parameter_lists_value        = [ label "var" . sto_to_spc_no_dquote ]
let parameter_lists_value_dquote = [ label "var"
                                     . del /"?/ "" . sto_to_spc_no_dquote
                                     . del /"?/ "" ]

let parameter_lists_values = parameter_lists_value_dquote
                           | ( sep_dquote . parameter_lists_value
                               . ( sep_cont . parameter_lists_value )+
                               . sep_dquote )

let parameter_lists_sep    = sep_cont_opt
                             . ( [ del "+" "+" . label "append" ]
                               | [ del "-" "-" . label "remove" ] )?
                             . del "=" "=" . sep_cont_opt

let parameter_lists        =
  negate_or_value
    (key parameter_lists_kw)
    (parameter_lists_sep . parameter_lists_values)

(************************************************************************
 * View: parameter
 *   A single parameter for <defaults>
 *
 *   Definition:
 *     > Parameter ::= Parameter '=' Value |
 *     >               Parameter '+=' Value |
 *     >               Parameter '-=' Value |
 *     >               '!'* Parameter
 *
 *     Parameters may be flags, integer values, strings, or lists.
 *
 *************************************************************************)
let parameter        = parameter_flag | parameter_integer
                     | parameter_string | parameter_lists

(************************************************************************
 * View: parameter_list
 *   A list of comma-separated <parameters> for <defaults>
 *
 *   Definition:
 *     > Parameter_List ::= Parameter |
 *     >                    Parameter ',' Parameter_List
 *************************************************************************)
let parameter_list   = parameter . ( sep_com . parameter )*

(************************************************************************
 * View: defaults
 *   A Defaults entry
 *
 *   Definition:
 *     > Default_Entry ::= Default_Type Parameter_List
 *************************************************************************)
let defaults = [ indent . key "Defaults" . default_type? . sep_cont
                   . parameter_list . comment_or_eol ]

(************************************************************************
 * Group:                     USER SPECIFICATION
 *************************************************************************)

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list> for listing
 *   users and/or groups used to run a command
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')' |
 *     >                '(:' Runas_List ')' |
 *     >                '(' Runas_List ':' Runas_List ')'
 *************************************************************************)
let runas_spec_user       = alias_list "runas_user" sto_to_com
let runas_spec_group      = Util.del_str ":" . indent
                            . alias_list "runas_group" sto_to_com

let runas_spec_usergroup  = runas_spec_user . indent . runas_spec_group

let runas_spec = Util.del_str "("
                 . (runas_spec_user
                    | runas_spec_group
                    | runas_spec_usergroup )
                 . Util.del_str ")" . sep_cont_opt

(************************************************************************
 * View: tag_spec
 *   Tag specification for <spec>
 *
 *   Definition:
 *     > Tag_Spec ::= ('NOPASSWD:' | 'PASSWD:' | 'NOEXEC:' | 'EXEC:' |
 *     >              'SETENV:' | 'NOSETENV:')
 *************************************************************************)
let tag_spec   =
  [ label "tag" . store /(NO)?(PASSWD|EXEC|SETENV)/ . sep_col ]

(************************************************************************
 * View: cmnd_spec
 *   Command specification for <spec>,
 *     with optional <runas_spec> and any amount of <tag_specs>
 *
 *   Definition:
 *     > Cmnd_Spec ::= Runas_Spec? Tag_Spec* Cmnd
 *************************************************************************)
let cmnd_spec  =
  [ label "command" .  runas_spec? . tag_spec* . sto_to_com_cmnd ]

(************************************************************************
 * View: cmnd_spec_list
 *   A list of comma-separated <cmnd_specs>
 *
 *   Definition:
 *     > Cmnd_Spec_List ::= Cmnd_Spec |
 *     >                    Cmnd_Spec ',' Cmnd_Spec_List
 *************************************************************************)
let cmnd_spec_list = cmnd_spec . ( sep_com . cmnd_spec )*

(************************************************************************
 * View: spec_list
 *   Group of hosts with <cmnd_spec_list>
 *************************************************************************)
let spec_list = [ label "host_group" . alias_list "host" sto_to_com_host
                    . sep_eq . cmnd_spec_list ]

(************************************************************************
 * View: spec
 *   A user specification, listing colon-separated <spec_lists>
 *
 *   Definition:
 *     > User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
 *     >               (':' Host_List '=' Cmnd_Spec_List)*
 *************************************************************************)
let spec = [ label "spec" . indent
               . alias_list "user" sto_to_com_user . sep_cont
               . spec_list
               . ( sep_col . spec_list )* . comment_or_eol ]

(************************************************************************
 * Group:                        LENS & FILTER
 *************************************************************************)

(* View: lns
     The sudoers lens, any amount of
       * <empty> lines
       * <comments>
       * <includedirs>
       * <aliases>
       * <defaults>
       * <specs>
*)
let lns = ( empty | comment | includedir | alias | defaults | spec  )*

(* View: filter *)
let filter = (incl "/etc/sudoers")
    . (incl "/usr/local/etc/sudoers")
    . (incl "/etc/sudoers.d/*")
    . (incl "/usr/local/etc/sudoers.d/*")
    . (incl "/opt/csw/etc/sudoers")
    . (incl "/etc/opt/csw/sudoers")
    . Util.stdexcl

let xfm = transform lns filter


Add some unit tests for sendmail_list lens


Nrpe: optimize regexp substractions

Nrpe: optimize regexp substractions

(*
Module: Nrpe
  Parses nagios-nrpe configuration files.

Author: Marc Fournier <marc.fournier@camptocamp.com>

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

module Nrpe =
  autoload xfm

let eol = Util.eol
let eq = Sep.equal

(* View: word *)
let word = /[^=\n\t ]+/

(* View: item_re *)
let item_re = /[^#=\n\t\/ ]+/ - (/command\[[^]\/\n]+\]/ | "include" | "include_dir")

(* View: command
    nrpe.cfg usually has many entries defining commands to run

    > command[check_foo]=/path/to/nagios/plugin -w 123 -c 456
    > command[check_bar]=/path/to/another/nagios/plugin --option
*)
let command =
  let obrkt = del /\[/ "[" in
  let cbrkt = del /\]/ "]" in
    [ key "command" .
    [ obrkt . key /[^]\/\n]+/ . cbrkt . eq
            . store /[^\n]+/ . del /\n/ "\n" ]
    ]

(* View: item
     regular entries

     > allow_bash_command_substitution=0
*)
let item = [ key item_re . eq . store word . eol ]

(* View: include
    An include entry.

    nrpe.cfg can include more than one file or directory of files

    > include=/path/to/file1.cfg
    > include=/path/to/file2.cfg
*)
let include = [ key "include" .
  [ label "file" . eq . store word . eol ]
]

(* View: include_dir
    > include_dir=/path/to/dir/
*)
let include_dir = [ key "include_dir" .
  [ label "dir" . eq . store word . eol ]
]

(* View: comment
    Nrpe comments must start at beginning of line *)
let comment = Util.comment_generic /#[ \t]*/ "# "

(* blank lines and empty comments *)
let empty = Util.empty

(* View: lns
    The Nrpe lens *)
let lns = ( command | include | include_dir | item | comment | empty ) *

(* View: filter
    File filter *)
let filter = incl "/etc/nrpe.cfg" .
             incl "/etc/nagios/nrpe.cfg"

let xfm = transform lns (filter)

(*
Module: Nrpe
  Parses nagios-nrpe configuration files.

Author: Marc Fournier <marc.fournier@camptocamp.com>

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

module Nrpe =
  autoload xfm

let eol = Util.eol
let eq = Sep.equal

(* View: word *)
let word = /[^=\n\t ]+/

(* View: item_re *)
let item_re = /[^#=\n\t\/ ]+/ - /command\[[^]\/\n]+\]/ - "include" - "include_dir"

(* View: command
    nrpe.cfg usually has many entries defining commands to run

    > command[check_foo]=/path/to/nagios/plugin -w 123 -c 456
    > command[check_bar]=/path/to/another/nagios/plugin --option
*)
let command =
  let obrkt = del /\[/ "[" in
  let cbrkt = del /\]/ "]" in
    [ key "command" .
    [ obrkt . key /[^]\/\n]+/ . cbrkt . eq
            . store /[^\n]+/ . del /\n/ "\n" ]
    ]

(* View: item
     regular entries

     > allow_bash_command_substitution=0
*)
let item = [ key item_re . eq . store word . eol ]

(* View: include
    An include entry.

    nrpe.cfg can include more than one file or directory of files

    > include=/path/to/file1.cfg
    > include=/path/to/file2.cfg
*)
let include = [ key "include" .
  [ label "file" . eq . store word . eol ]
]

(* View: include_dir
    > include_dir=/path/to/dir/
*)
let include_dir = [ key "include_dir" .
  [ label "dir" . eq . store word . eol ]
]

(* View: comment
    Nrpe comments must start at beginning of line *)
let comment = Util.comment_generic /#[ \t]*/ "# "

(* blank lines and empty comments *)
let empty = Util.empty

(* View: lns
    The Nrpe lens *)
let lns = ( command | include | include_dir | item | comment | empty ) *

(* View: filter
    File filter *)
let filter = incl "/etc/nrpe.cfg" .
             incl "/etc/nagios/nrpe.cfg"

let xfm = transform lns (filter)


Csv: remove unused caret from comment regexp

Fixes #396

(*
Module: CSV
  Generic CSV lens collection

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

About: Reference
  https://tools.ietf.org/html/rfc4180

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

About: Lens Usage
   To be documented

About: Configuration files

About: Examples
   The <Test_CSV> file contains various examples and tests.

Caveats:
   No support for files without an ending CRLF
*)
module CSV =

(* View: eol *)
let eol = Util.del_str "\n"

(* View: comment *)
let comment = Util.comment
            | [ del /#[ \t]*\r?\n/ "#\n" ]

(* View: entry
     An entry of fields, quoted or not *)
let entry (sep_str:string) =
  let field = [ seq "field" . store (/[^"#\r\n]/ - sep_str)* ]
            | [ seq "field" . store /("[^"#]*")+/ ]
  in let sep = Util.del_str sep_str
  in [ seq "entry" . counter "field" . Build.opt_list field sep . eol ]

(* View: lns
     The generic lens, taking the separator as a parameter *)
let lns_generic (sep:string) = (comment | entry sep)*

(* View: lns
     The comma-separated value lens *)
let lns = lns_generic ","

(* View: lns_semicol
     A semicolon-separated value lens *)
let lns_semicol = lns_generic ";"

(*
Module: CSV
  Generic CSV lens collection

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

About: Reference
  https://tools.ietf.org/html/rfc4180

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

About: Lens Usage
   To be documented

About: Configuration files

About: Examples
   The <Test_CSV> file contains various examples and tests.

Caveats:
   No support for files without an ending CRLF
*)
module CSV =

(* View: eol *)
let eol = Util.del_str "\n"

(* View: comment *)
let comment = Util.comment
            | [ del /^#[ \t]*\r?\n/ "#\n" ]

(* View: entry
     An entry of fields, quoted or not *)
let entry (sep_str:string) =
  let field = [ seq "field" . store (/[^"#\r\n]/ - sep_str)* ]
            | [ seq "field" . store /("[^"#]*")+/ ]
  in let sep = Util.del_str sep_str
  in [ seq "entry" . counter "field" . Build.opt_list field sep . eol ]

(* View: lns
     The generic lens, taking the separator as a parameter *)
let lns_generic (sep:string) = (comment | entry sep)*

(* View: lns
     The comma-separated value lens *)
let lns = lns_generic ","

(* View: lns_semicol
     A semicolon-separated value lens *)
let lns_semicol = lns_generic ";"


qemu: fix double space in augeas file

qemu: fix double space in augeas file

Signed-off-by: Ján Tomko <4cab11cfb98d3c937327354a78eb07dbb6ee2bc6@redhat.com>
Reviewed-by: John Ferlan <87558058f6f829e5ec976c8ef960720af4ff9c7d@redhat.com>

(* /etc/libvirt/qemu.conf *)

module Libvirtd_qemu =
   autoload xfm

   let eol   = del /[ \t]*\n/ "\n"
   let value_sep   = del /[ \t]*=[ \t]*/  " = "
   let indent = del /[ \t]*/ ""

   let array_sep  = del /,[ \t\n]*/ ", "
   let array_start = del /\[[ \t\n]*/ "[ "
   let array_end = del /\]/ "]"

   let str_val = del /\"/ "\"" . store /[^\"]*/ . del /\"/ "\""
   let bool_val = store /0|1/
   let int_val = store /[0-9]+/
   let str_array_element = [ seq "el" . str_val ] . del /[ \t\n]*/ ""
   let str_array_val = counter "el" . array_start . ( str_array_element . ( array_sep . str_array_element ) * ) ? . array_end

   let str_entry       (kw:string) = [ key kw . value_sep . str_val ]
   let bool_entry      (kw:string) = [ key kw . value_sep . bool_val ]
   let int_entry       (kw:string) = [ key kw . value_sep . int_val ]
   let str_array_entry (kw:string) = [ key kw . value_sep . str_array_val ]

   let unlimited_val =  del /\"/ "\"" . store /unlimited/ . del /\"/ "\""
   let limits_entry (kw:string) = [ key kw . value_sep . unlimited_val ] |  [ key kw . value_sep . int_val ]

   (* Config entry grouped by function - same order as example config *)
   let default_tls_entry = str_entry "default_tls_x509_cert_dir"
                 | bool_entry "default_tls_x509_verify"
                 | str_entry "default_tls_x509_secret_uuid"

   let vnc_entry = str_entry "vnc_listen"
                 | bool_entry "vnc_auto_unix_socket"
                 | bool_entry "vnc_tls"
                 | str_entry "vnc_tls_x509_cert_dir"
                 | bool_entry "vnc_tls_x509_verify"
                 | str_entry "vnc_password"
                 | bool_entry "vnc_sasl"
                 | str_entry "vnc_sasl_dir"
                 | bool_entry "vnc_allow_host_audio"

   let spice_entry = str_entry "spice_listen"
                 | bool_entry "spice_tls"
                 | str_entry  "spice_tls_x509_cert_dir"
                 | bool_entry "spice_auto_unix_socket"
                 | str_entry "spice_password"
                 | bool_entry "spice_sasl"
                 | str_entry "spice_sasl_dir"

   let chardev_entry = bool_entry "chardev_tls"
                 | str_entry "chardev_tls_x509_cert_dir"
                 | bool_entry "chardev_tls_x509_verify"
                 | str_entry "chardev_tls_x509_secret_uuid"

   let migrate_entry = str_entry "migrate_tls_x509_cert_dir"
                 | bool_entry "migrate_tls_x509_verify"
                 | str_entry "migrate_tls_x509_secret_uuid"

   let nogfx_entry = bool_entry "nographics_allow_host_audio"

   let remote_display_entry = int_entry "remote_display_port_min"
                 | int_entry "remote_display_port_max"
                 | int_entry "remote_websocket_port_min"
                 | int_entry "remote_websocket_port_max"

   let security_entry = str_entry "security_driver"
                 | bool_entry "security_default_confined"
                 | bool_entry "security_require_confined"
                 | str_entry "user"
                 | str_entry "group"
                 | bool_entry "dynamic_ownership"
                 | str_array_entry "cgroup_controllers"
                 | str_array_entry "cgroup_device_acl"
                 | int_entry "seccomp_sandbox"
                 | str_array_entry "namespaces"

   let save_entry = str_entry "save_image_format"
                 | str_entry "dump_image_format"
                 | str_entry "snapshot_image_format"
                 | str_entry "auto_dump_path"
                 | bool_entry "auto_dump_bypass_cache"
                 | bool_entry "auto_start_bypass_cache"

   let process_entry = str_entry "hugetlbfs_mount"
                 | bool_entry "clear_emulator_capabilities"
                 | str_entry "bridge_helper"
                 | str_entry "pr_helper"
                 | bool_entry "set_process_name"
                 | int_entry "max_processes"
                 | int_entry "max_files"
                 | limits_entry "max_core"
                 | bool_entry "dump_guest_core"
                 | str_entry "stdio_handler"

   let device_entry = bool_entry "mac_filter"
                 | bool_entry "relaxed_acs_check"
                 | bool_entry "allow_disk_format_probing"
                 | str_entry "lock_manager"

   let rpc_entry = int_entry "max_queued"
                 | int_entry "keepalive_interval"
                 | int_entry "keepalive_count"

   let network_entry = str_entry "migration_address"
                 | int_entry "migration_port_min"
                 | int_entry "migration_port_max"
                 | str_entry "migration_host"

   let log_entry = bool_entry "log_timestamp"

   let nvram_entry = str_array_entry "nvram"

   let gluster_debug_level_entry = int_entry "gluster_debug_level"

   let memory_entry = str_entry "memory_backing_dir"

   let vxhs_entry = bool_entry "vxhs_tls"
                 | str_entry "vxhs_tls_x509_cert_dir"

   let nbd_entry = bool_entry "nbd_tls"
                | str_entry "nbd_tls_x509_cert_dir"

   let swtpm_user_entry = str_entry "swtpm_user"
   let swtpm_group_entry = str_entry "swtpm_group"

   (* Each entry in the config is one of the following ... *)
   let entry = default_tls_entry
             | vnc_entry
             | spice_entry
             | chardev_entry
             | migrate_entry
             | nogfx_entry
             | remote_display_entry
             | security_entry
             | save_entry
             | process_entry
             | device_entry
             | rpc_entry
             | network_entry
             | log_entry
             | nvram_entry
             | gluster_debug_level_entry
             | memory_entry
             | vxhs_entry
             | nbd_entry
             | swtpm_user_entry
             | swtpm_group_entry

   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
   let empty = [ label "#empty" . eol ]

   let record = indent . entry . eol

   let lns = ( record | comment | empty ) *

   let filter = incl "/etc/libvirt/qemu.conf"
              . Util.stdexcl

   let xfm = transform lns filter


Util.stdexcl: exclude .augsave and .augnew files

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

About: License
  This file is licensed under the GPL.
*)

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: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment and empty
     This is a general definition of comment and empty.
     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
  Map comments into "#comment" nodes
*)
  let comment =
    [ indent . label "#comment" . del /#[ \t]*/ "# "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

(*
View: empty
  Map empty lines, including empty comments
*)
  let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(* 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 ] ) *

(* Group: Exclusions

Variable: stdexcl
  Exclusion for files that are commonly not wanted/needed
*)
  let stdexcl = (excl "*~") .
    (excl "*.rpmnew") .
    (excl "*.rpmsave") .
    (excl "*.augsave") .
    (excl "*.augnew")

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

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

About: License
  This file is licensed under the GPL.
*)

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: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment and empty
     This is a general definition of comment and empty.
     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
  Map comments into "#comment" nodes
*)
  let comment =
    [ indent . label "#comment" . del /#[ \t]*/ "# "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

(*
View: empty
  Map empty lines, including empty comments
*)
  let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

(* 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 ] ) *

(* Group: Exclusions

Variable: stdexcl
  Exclusion for files that are commonly not wanted/needed
*)
  let stdexcl = (excl "*~") . (excl "*.rpmnew") . (excl "*.rpmsave")

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