Shellvars.aug: Fix empty lines after comments

(*
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 eol = del /[ \t]+|[ \t]*(;|\n)\n*/ "\n"
  let semicol_eol = del /[ \t]*(;|\n)\n*/ "\n"
  let empty   = Util.empty

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

  let comment = Util.comment . empty*
  (* comment_eol in shell MUST begin with a space *)
  let comment_eol = Util.comment_generic /[ \t]+#[ \t]*/ " # "
  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) 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"

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

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

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

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

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

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

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

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

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

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

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

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry*
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store char+
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . (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_excl "network" .
      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/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"

  let filter = filter_sysconfig
             . filter_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 eol = del /[ \t]+|[ \t]*(;|\n)\n*/ "\n"
  let semicol_eol = del /[ \t]*(;|\n)\n*/ "\n"

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

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

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

  let char  = /[^`;() '"\t\n]|\\\\"/
  let dquot = /"([^"\\]|\\\\.)*"/                    (* " 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) 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"

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

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

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

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

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

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

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

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

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

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

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

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry*
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store char+
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . ((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 | empty | source | kv
              | unset | bare_export | builtin | return | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry
      | function entry

  let lns_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_excl "network" .
      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/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"

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

  let xfm = transform lns filter

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


Added /etc/sysconfig/suseconfig to shellvars lens.

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

  let eol = Util.eol

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

  let char  = /[^() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  let bquot = /`[^`\n]*`/

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

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

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

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

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

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

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

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

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

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

  let xfm = transform lns filter

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

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

  let eol = Util.eol

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

  let char  = /[^() '"\t\n]|\\\\"/
  let dquot = /"([^"\\\n]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^'\n]*'/
  let bquot = /`[^`\n]*`/

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

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

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

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

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

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

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

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

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

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

  let xfm = transform lns filter

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


Use generic modules Rx, Sep and Build in keepalived.aug

(*
Module: Keepalived
  Parses /etc/keepalived/keepalived.conf

Author: Raphael Pinson <raphink@gmail.com>

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

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

About: Lens Usage
   To be documented

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

module Keepalived =
  autoload xfm

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

(* Group: Comments and empty lines *)

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

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

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

(* View: lbracket *)
let lbracket = Util.del_str "{"

(* View: rbracket *)
let rbracket = Util.del_str "}"

(* View: comment
Map comments in "#comment" nodes *)
let comment =
    [ indent . label "#comment" . del /[#!][ \t]*/ "# "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

(* View: eol_comment
And end-of-line comment
with a space in front of # by default *)
let eol_comment =
    [ label "#comment" . del /[ \t]*[#!][ \t]*/ " # "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

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

(* View: sto_email_addr *)
let sto_email_addr = store Rx.email_addr

(* Variable: word *)
let word = Rx.word

(* Variable: word_slash *)
let word_slash = word | "/"

(* View: sto_word *)
let sto_word = store word

(* View: sto_num *)
let sto_num = store Rx.integer

(* View: field *)
let field (kw:string) (sto:lens) = indent . Build.key_value_line_comment kw sep_spc sto eol_comment

(* View: flag
A single word *)
let flag (kw:regexp) = indent . Build.flag kw

(* View: lens_block
A generic block with a title lens *)
let lens_block (title:lens) (sto:lens) = [ indent . title . sep_spc . lbracket . eol
                                         . (sto | empty | comment)+
                                         . indent . rbracket . eol ]

(* View: block
A simple block with just a block title *)
let block (kw:string) (sto:lens) = lens_block (key kw) sto

(* View: named_block
A block with a block title and name *)
let named_block (kw:string) (sto:lens) = lens_block (key kw . sep_spc . sto_word) sto

(* View: named_block_arg_title
A title lens for named_block_arg *)
let named_block_arg_title (kw:string) (name:string) (arg:string) =
                            key kw . sep_spc
                          . [ label name . sto_word ]
                          . sep_spc
                          . [ label arg . sto_word ]

(* View: named_block_arg
A block with a block title, a name and an argument *)
let named_block_arg (kw:string) (name:string) (arg:string) (sto:lens) =
                           lens_block (named_block_arg_title kw name arg) sto

(************************************************************************
 * Group:                 GLOBAL CONFIGURATION
 *************************************************************************)

(* View: email
A simple email address entry *)
let email = [ indent . label "email" . sto_email_addr . (eol_comment|eol) ]

(* View: global_defs_field
Possible fields in the global_defs block *)
let global_defs_field = block "notification_email" email
                      | field "notification_email_from" sto_email_addr
                      | field "smtp_server" sto_word
                      | field "smtp_connect_timeout" sto_num
                      | field "lvs_id" sto_word
                      | field "router_id" sto_word

(* View: global_defs
A global_defs block *)
let global_defs = block "global_defs" global_defs_field

(* View: prefixlen
A prefix for IP addresses *)
let prefixlen = [ label "prefixlen" . Util.del_str "/" . sto_num ]

(* View: ipaddr
An IP address or range with an optional mask *)
let ipaddr = label "ipaddr" . store /[0-9\.-]+/ . prefixlen?

(* View: ipdev
A device for IP addresses *)
let ipdev = [ key "dev" . sep_spc . sto_word ]

(* View: static_ipaddress_field
The whole string is fed to ip addr add.
You can truncate the string anywhere you like and let ip addr add use defaults for the rest of the string.
To be refined with fields according to `ip addr help`.
*)
let static_ipaddress_field = [ indent . ipaddr
                             . (sep_spc . ipdev)?
                             . (eol|eol_comment) ]

(* View: static_routes_field
src $SRC_IP to $DST_IP dev $SRC_DEVICE
*)
let static_routes_field = [ indent . label "route"
                          . [ key "src" . sto_word ] . sep_spc
                          . [ key "to"  . sto_word ] . sep_spc
                          . [ key "dev" . sto_word ] . (eol|eol_comment) ]

(* View: static_routes *)
let static_routes = block "static_ipaddress" static_ipaddress_field
                  | block "static_routes" static_routes_field

(* View: global_conf
A global configuration entry *)
let global_conf = global_defs | static_routes

(************************************************************************
 * Group:                 VRRP CONFIGURATION
 *************************************************************************)

(*View: vrrp_sync_group_field *)
let vrrp_sync_group_field = block "group" [ indent . key word . (eol|eol_comment) ]

(* View: vrrp_sync_group *)
let vrrp_sync_group = named_block "vrrp_sync_group" vrrp_sync_group_field

(* View: vrrp_instance_field *)
let vrrp_instance_field = field "state" sto_word
                        | field "interface" sto_word
                        | field "lvs_sync_daemon_interface" sto_word
                        | field "virtual_router_id" sto_num
                        | field "priority" sto_num
                        | field "advert_int" sto_num
                        | flag "smtp_alert"
                        | flag "nopreempt"
                        | block "authentication" (
                                field "auth_type" sto_word
                              | field "auth_pass" sto_word
                              )
                        | block "virtual_ipaddress" static_ipaddress_field

(* View: vrrp_instance *)
let vrrp_instance = named_block "vrrp_instance" vrrp_instance_field

(* View: vrrpd_conf
contains subblocks of VRRP synchronization group(s) and VRRP instance(s) *)
let vrrpd_conf = vrrp_sync_group | vrrp_instance

(************************************************************************
 * Group:                 LVS CONFIGURATION
 *************************************************************************)

(* View: tcp_check_field *)
let tcp_check_field = field "connect_timeout" sto_num
                    | field "connect_port" sto_num

(* View: real_server_field *)
let real_server_field = field "weight" sto_num
                      | block "TCP_CHECK" tcp_check_field

(* View: virtual_server_field *)
let virtual_server_field = field "delay_loop" sto_num
                         | field "lb_algo" sto_word
                         | field "lb_kind" sto_word
                         | field "nat_mask" sto_word
                         | field "protocol" sto_word
                         | named_block_arg "real_server" "ip" "port" real_server_field

(* View: virtual_server *)
let virtual_server = named_block_arg "virtual_server" "ip" "port" virtual_server_field

(* View: virtual_server_group_field *)
let virtual_server_group_field = [ indent . label "vip"
                               . [ ipaddr ]
			       . sep_spc
			       . [ label "port" . sto_num ]
			       . (eol|eol_comment) ]

(* View: virtual_server_group *)
let virtual_server_group = named_block "virtual_server_group" virtual_server_group_field

(* View: lvs_conf
contains subblocks of Virtual server group(s) and Virtual server(s) *)
let lvs_conf = virtual_server | virtual_server_group

(* View: lns
     The keepalived lens
*)
let lns = ( empty | comment | global_conf | vrrpd_conf | lvs_conf )*

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

let xfm = transform lns filter

(*
Module: Keepalived
  Parses /etc/keepalived/keepalived.conf

Author: Raphael Pinson <raphink@gmail.com>

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

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

About: Lens Usage
   To be documented

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

module Keepalived =
  autoload xfm

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

(* Group: Comments and empty lines *)

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

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

(* View: sep_spc *)
let sep_spc = del /[ \t]+/ " "

(* View: lbracket *)
let lbracket = Util.del_str "{"

(* View: rbracket *)
let rbracket = Util.del_str "}"

(* View: comment
Map comments in "#comment" nodes *)
let comment =
    [ indent . label "#comment" . del /[#!][ \t]*/ "# "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

(* View: eol_comment
And end-of-line comment
with a space in front of # by default *)
let eol_comment =
    [ label "#comment" . del /[ \t]*[#!][ \t]*/ " # "
        . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . eol ]

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

(* View: sto_email_addr *)
let sto_email_addr = store /[A-Za-z0-9_\+\.-]+@[A-Za-z0-9_\.-]+/

(* Variable: word *)
let word = /[A-Za-z0-9_\.-]+/

(* Variable: word_slash *)
let word_slash = /[A-Za-z0-9_\.\/-]+/

(* View: sto_word *)
let sto_word = store word

(* View: sto_num *)
let sto_num = store /[0-9]+/

(* View: field *)
let field (kw:string) (sto:lens) = [ indent . key kw . sep_spc . sto . (eol_comment|eol) ]

(* View: flag
A single word *)
let flag (kw:regexp) = [ indent . key kw ]

(* View: lens_block
A generic block with a title lens *)
let lens_block (title:lens) (sto:lens) = [ indent . title . sep_spc . lbracket . eol
                                         . (sto | empty | comment)+
                                         . indent . rbracket . eol ]

(* View: block
A simple block with just a block title *)
let block (kw:string) (sto:lens) = lens_block (key kw) sto

(* View: named_block
A block with a block title and name *)
let named_block (kw:string) (sto:lens) = lens_block (key kw . sep_spc . sto_word) sto

(* View: named_block_arg_title
A title lens for named_block_arg *)
let named_block_arg_title (kw:string) (name:string) (arg:string) =
                            key kw . sep_spc
                          . [ label name . sto_word ]
                          . sep_spc
                          . [ label arg . sto_word ]

(* View: named_block_arg
A block with a block title, a name and an argument *)
let named_block_arg (kw:string) (name:string) (arg:string) (sto:lens) =
                           lens_block (named_block_arg_title kw name arg) sto

(************************************************************************
 * Group:                 GLOBAL CONFIGURATION
 *************************************************************************)

(* View: email
A simple email address entry *)
let email = [ indent . label "email" . sto_email_addr . (eol_comment|eol) ]

(* View: global_defs_field
Possible fields in the global_defs block *)
let global_defs_field = block "notification_email" email
                      | field "notification_email_from" sto_email_addr
                      | field "smtp_server" sto_word
                      | field "smtp_connect_timeout" sto_num
                      | field "lvs_id" sto_word
                      | field "router_id" sto_word

(* View: global_defs
A global_defs block *)
let global_defs = block "global_defs" global_defs_field

(* View: prefixlen
A prefix for IP addresses *)
let prefixlen = [ label "prefixlen" . Util.del_str "/" . sto_num ]

(* View: ipaddr
An IP address or range with an optional mask *)
let ipaddr = label "ipaddr" . store /[0-9\.-]+/ . prefixlen?

(* View: ipdev
A device for IP addresses *)
let ipdev = [ key "dev" . sep_spc . sto_word ]

(* View: static_ipaddress_field
The whole string is fed to ip addr add.
You can truncate the string anywhere you like and let ip addr add use defaults for the rest of the string.
To be refined with fields according to `ip addr help`.
*)
let static_ipaddress_field = [ indent . ipaddr
                             . (sep_spc . ipdev)?
                             . (eol|eol_comment) ]

(* View: static_routes_field
src $SRC_IP to $DST_IP dev $SRC_DEVICE
*)
let static_routes_field = [ indent . label "route"
                          . [ key "src" . sto_word ] . sep_spc
                          . [ key "to"  . sto_word ] . sep_spc
                          . [ key "dev" . sto_word ] . (eol|eol_comment) ]

(* View: static_routes *)
let static_routes = block "static_ipaddress" static_ipaddress_field
                  | block "static_routes" static_routes_field

(* View: global_conf
A global configuration entry *)
let global_conf = global_defs | static_routes

(************************************************************************
 * Group:                 VRRP CONFIGURATION
 *************************************************************************)

(*View: vrrp_sync_group_field *)
let vrrp_sync_group_field = block "group" [ indent . key word . (eol|eol_comment) ]

(* View: vrrp_sync_group *)
let vrrp_sync_group = named_block "vrrp_sync_group" vrrp_sync_group_field

(* View: vrrp_instance_field *)
let vrrp_instance_field = field "state" sto_word
                        | field "interface" sto_word
                        | field "lvs_sync_daemon_interface" sto_word
                        | field "virtual_router_id" sto_num
                        | field "priority" sto_num
                        | field "advert_int" sto_num
                        | flag "smtp_alert"
                        | flag "nopreempt"
                        | block "authentication" (
                                field "auth_type" sto_word
                              | field "auth_pass" sto_word
                              )
                        | block "virtual_ipaddress" static_ipaddress_field

(* View: vrrp_instance *)
let vrrp_instance = named_block "vrrp_instance" vrrp_instance_field

(* View: vrrpd_conf
contains subblocks of VRRP synchronization group(s) and VRRP instance(s) *)
let vrrpd_conf = vrrp_sync_group | vrrp_instance

(************************************************************************
 * Group:                 LVS CONFIGURATION
 *************************************************************************)

(* View: tcp_check_field *)
let tcp_check_field = field "connect_timeout" sto_num
                    | field "connect_port" sto_num

(* View: real_server_field *)
let real_server_field = field "weight" sto_num
                      | block "TCP_CHECK" tcp_check_field

(* View: virtual_server_field *)
let virtual_server_field = field "delay_loop" sto_num
                         | field "lb_algo" sto_word
                         | field "lb_kind" sto_word
                         | field "nat_mask" sto_word
                         | field "protocol" sto_word
                         | named_block_arg "real_server" "ip" "port" real_server_field

(* View: virtual_server *)
let virtual_server = named_block_arg "virtual_server" "ip" "port" virtual_server_field

(* View: virtual_server_group_field *)
let virtual_server_group_field = [ indent . label "vip"
                               . [ ipaddr ]
			       . sep_spc
			       . [ label "port" . sto_num ]
			       . (eol|eol_comment) ]

(* View: virtual_server_group *)
let virtual_server_group = named_block "virtual_server_group" virtual_server_group_field

(* View: lvs_conf
contains subblocks of Virtual server group(s) and Virtual server(s) *)
let lvs_conf = virtual_server | virtual_server_group

(* View: lns
     The keepalived lens
*)
let lns = ( empty | comment | global_conf | vrrpd_conf | lvs_conf )*

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

let xfm = transform lns filter


Make trapperkeeper lens compatible with 0.10

(*
Module: Trapperkeeper
  Parses Trapperkeeper configuration files

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 Trapperkeeper webservice configuration files. See <filter>.

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

autoload xfm

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

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

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

(* View: sep *)
let sep = del /[ \t]*[:=]/ ":"

(* View: sep_with_spc *)
let sep_with_spc = sep . Sep.opt_space

(************************************************************************
 * Group:       BLOCKS (FROM 1.2, FOR 0.10 COMPATIBILITY)
 *************************************************************************)

(* Variable: block_ldelim_newlines_re *)
let block_ldelim_newlines_re = /[ \t\n]+\{([ \t\n]*\n)?/

(* Variable: block_rdelim_newlines_re *)
let block_rdelim_newlines_re = /[ \t]*\}/

(* Variable: block_ldelim_newlines_default *)
let block_ldelim_newlines_default = "\n{\n"

(* Variable: block_rdelim_newlines_default *)
let block_rdelim_newlines_default = "}"

(************************************************************************
 * View: block_newline
 *   A block enclosed in brackets, with newlines forced
 *   and indentation defaulting to a tab.
 *
 *   Parameters:
 *     entry:lens - the entry to be stored inside the block.
 *                  This entry should not include <Util.empty>,
 *                  <Util.comment> or <Util.comment_noindent>,
 *                  should be indented and finish with an eol.
 ************************************************************************)
let block_newlines (entry:lens) (comment:lens) =
      let indent = del /[ \t]*/ "\t"
   in del block_ldelim_newlines_re block_ldelim_newlines_default
 . ((entry | comment) . (Util.empty | entry | comment)*)?
 . del block_rdelim_newlines_re block_rdelim_newlines_default

(************************************************************************
 * Group:                 ENTRY TYPES
 *************************************************************************)

(* View: simple *)
let simple = [ Util.indent . key Rx.word
             . sep_with_spc . store /[^\[ \t\n]+/ . Util.eol ]

(* View: array *)
let array =
     let lbrack = Util.del_str "["
  in let rbrack = Util.del_str "]"
  in let opt_space = del /[ \t]*/ ""
  in let comma = opt_space . Util.del_str "," . opt_space
  in let elem = [ seq "elem" . store Rx.neg1 ]
  in let elems = counter "elem" . Build.opt_list elem comma
  in [ Util.indent . key Rx.word
     . sep_with_spc . lbrack . Sep.opt_space
     . (elems . Sep.opt_space)?
     . rbrack . Util.eol ]

(* View: hash *)
let hash (lns:lens) = [ Util.indent . key Rx.word . sep
               . block_newlines lns Util.comment
               . Util.eol ]

(************************************************************************
 * Group:                   ENTRY
 *************************************************************************)

(* Just for typechecking *)
let entry_no_rec = hash (simple|array)

(* View: entry *)
let rec entry = hash (entry|simple|array)

(************************************************************************
 * Group:                LENS AND FILTER
 *************************************************************************)

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

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

let xfm = transform lns filter

(*
Module: Trapperkeeper
  Parses Trapperkeeper configuration files

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 Trapperkeeper webservice configuration files. See <filter>.

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

autoload xfm

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

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

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

(* View: sep *)
let sep = del /[ \t]*[:=]/ ":"

(* View: sep_with_spc *)
let sep_with_spc = sep . Sep.opt_space

(************************************************************************
 * Group:                 ENTRY TYPES
 *************************************************************************)

(* View: simple *)
let simple = [ Util.indent . key Rx.word
             . sep_with_spc . store /[^\[ \t\n]+/ . Util.eol ]

(* View: array *)
let array =
     let lbrack = Util.del_str "["
  in let rbrack = Util.del_str "]"
  in let comma = Util.delim ","
  in let elem = [ seq "elem" . store Rx.neg1 ]
  in let elems = counter "elem" . Build.opt_list elem comma
  in [ Util.indent . key Rx.word
     . sep_with_spc . lbrack . Sep.opt_space
     . (elems . Sep.opt_space)?
     . rbrack . Util.eol ]

(* View: hash *)
let hash (lns:lens) = [ Util.indent . key Rx.word . sep
               . Build.block_newlines lns Util.comment
               . Util.eol ]

(************************************************************************
 * Group:                   ENTRY
 *************************************************************************)

(* Just for typechecking *)
let entry_no_rec = hash (simple|array)

(* View: entry *)
let rec entry = hash (entry|simple|array)

(************************************************************************
 * Group:                LENS AND FILTER
 *************************************************************************)

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

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

let xfm = transform lns filter


Shellvars: Add test for [[]]

Shellvars: Add test for [[]]

(* Test for shell lens *)
module Test_shellvars =

  let lns = Shellvars.lns

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  a)
    B=C
    ;;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(* Test for shell lens *)
module Test_shellvars =

  let lns = Shellvars.lns

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  a)
    B=C
    ;;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Parse pam.conf in pam.aug

Parse pam.conf in pam.aug

(* Proces /etc/pam.d *)
module Pam =
  autoload xfm

  let eol = Util.eol
  let indent = Util.indent

  (* For the control syntax of [key=value ..] we could split the key value *)
  (* pairs into an array and generate a subtree control/N/KEY = VALUE      *)
  let control = /(\[[^]#\n]*\]|[^[ \t][^ \t]*)/
  let word = /[^# \t\n]+/
  (* Allowed types. FIXME: Should be case insensitive *)
  let types = /(auth|session|account|password)/

  (* This isn't entirely right: arguments enclosed in [ .. ] are allowed   *)
  (* and should be parsed as one                                           *)
  let argument = /[^#\n \t]+/

  let comment = Util.comment
  let comment_or_eol = Util.comment_or_eol
  let empty   = Util.empty

  (* Not mentioned in the man page, but Debian uses the syntax             *)
  (*   @include module                                                     *)
  (* quite a bit                                                           *)
  let include = [ indent . Util.del_str "@" . key "include" .
                  Util.del_ws_spc . store word . eol ]

  let record = [ seq "record" . indent .
                   [ label "optional" . del "-" "-" ]? .
                   [ label "type" . store types ] .
                   Util.del_ws_tab .
                   [ label "control" . store control] .
                   Util.del_ws_tab .
                   [ label "module" . store word ] .
                   [ Util.del_ws_tab . label "argument" . store argument ]* .
		 comment_or_eol
               ]
  let lns = ( empty | comment | include | record ) *

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

  let xfm = transform lns filter

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

(* Proces /etc/pam.d *)
module Pam =
  autoload xfm

  let eol = Util.eol
  let indent = Util.indent

  (* For the control syntax of [key=value ..] we could split the key value *)
  (* pairs into an array and generate a subtree control/N/KEY = VALUE      *)
  let control = /(\[[^]#\n]*\]|[^[ \t][^ \t]*)/
  let word = /[^# \t\n]+/
  (* Allowed types. FIXME: Should be case insensitive *)
  let types = /(auth|session|account|password)/

  (* This isn't entirely right: arguments enclosed in [ .. ] are allowed   *)
  (* and should be parsed as one                                           *)
  let argument = /[^#\n \t]+/

  let comment = Util.comment
  let comment_or_eol = Util.comment_or_eol
  let empty   = Util.empty

  (* Not mentioned in the man page, but Debian uses the syntax             *)
  (*   @include module                                                     *)
  (* quite a bit                                                           *)
  let include = [ indent . Util.del_str "@" . key "include" .
                  Util.del_ws_spc . store word . eol ]

  let record = [ seq "record" . indent .
                   [ label "optional" . del "-" "-" ]? .
                   [ label "type" . store types ] .
                   Util.del_ws_tab .
                   [ label "control" . store control] .
                   Util.del_ws_tab .
                   [ label "module" . store word ] .
                   [ Util.del_ws_tab . label "argument" . store argument ]* .
		 comment_or_eol
               ]
  let lns = ( empty | comment | include | record ) *

  let xfm = transform lns ((incl "/etc/pam.d/*") . Util.stdexcl)

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


Ssh: allow indented commas_entry

Ssh: allow indented commas_entry

(*
Module: Ssh
  Parses ssh client configuration

Author: Jiri Suchomel <jsuchome@suse.cz>

About: Reference
    ssh_config man page

About: License
    This file is licensed under the GPL.

About: Lens Usage
  Sample usage of this lens in augtool

(start code)
augtool> set /files/etc/ssh/ssh_config/Host example.com
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/RemoteForward/machine1:1234 machine2:5678
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/Ciphers/1 aes128-ctr
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/Ciphers/2 aes192-ctr
(end code)

*)

module Ssh =
    autoload xfm

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

    let eol = Util.doseol
    let spc = Util.del_ws_spc

    let comment = Util.comment
    let empty = Util.empty
    let comma = Util.del_str ","
    let indent = Util.indent
    let value_to_eol = store Rx.space_in
    let value_to_spc = store Rx.no_spaces
    let value_to_comma = store /[^, \t\r\n]+/

(************************************************************************
 * Group:                 ENTRIES
 *************************************************************************)

    let array_entry (k:regexp) =
        [ indent . key k . counter "array_entry"
         . [ spc . seq "array_entry" . value_to_spc]* . eol ]

    let commas_entry (k:regexp) =
         let value = [ seq "commas_entry" . value_to_comma]
      in [ indent . key k . counter "commas_entry" . spc .
           Build.opt_list value comma . eol ]

    let fw_entry (k:regexp) = [ indent . key k . spc .
	    [ key /[^ \t\r\n\/]+/ . spc . value_to_eol . eol ]]

    let send_env = array_entry /SendEnv/i

    let proxy_command = [ indent . key /ProxyCommand/i . spc . value_to_eol . eol ]

    let remote_fw = fw_entry /RemoteForward/i
    let local_fw = fw_entry /LocalForward/i

    let ciphers = commas_entry /Ciphers/i
    let macs	= commas_entry /MACs/i

    let special_entry = send_env
	                    | proxy_command
	                    | remote_fw
	                    | local_fw
	                    | macs
	                    | ciphers

    let key_re = /[A-Za-z0-9]+/
               - /SendEnv|Host|ProxyCommand|RemoteForward|LocalForward|MACs|Ciphers/i

    let other_entry = [ indent . key key_re
                    . spc . value_to_spc . eol ]

    let entry = comment | empty
              | special_entry
	            | other_entry

    let host = [ key /Host/i . spc . value_to_eol . eol . entry* ]

(************************************************************************
 * Group:                 LENS
 *************************************************************************)

    let lns = entry* . host*

    let xfm = transform lns (incl "/etc/ssh/ssh_config" .
                             incl (Sys.getenv("HOME") . "/.ssh/config"))

(*
Module: Ssh
  Parses ssh client configuration

Author: Jiri Suchomel <jsuchome@suse.cz>

About: Reference
    ssh_config man page

About: License
    This file is licensed under the GPL.

About: Lens Usage
  Sample usage of this lens in augtool

(start code)
augtool> set /files/etc/ssh/ssh_config/Host example.com
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/RemoteForward/machine1:1234 machine2:5678
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/Ciphers/1 aes128-ctr
augtool> set /files/etc/ssh/ssh_config/Host[.='example.com']/Ciphers/2 aes192-ctr
(end code)

*)

module Ssh =
    autoload xfm

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

    let eol = Util.doseol
    let spc = Util.del_ws_spc

    let comment = Util.comment
    let empty = Util.empty
    let comma = Util.del_str ","
    let indent = Util.indent
    let value_to_eol = store Rx.space_in
    let value_to_spc = store Rx.no_spaces
    let value_to_comma = store /[^, \t\r\n]+/

(************************************************************************
 * Group:                 ENTRIES
 *************************************************************************)

    let array_entry (k:regexp) =
        [ indent . key k . counter "array_entry"
         . [ spc . seq "array_entry" . value_to_spc]* . eol ]

    let commas_entry (k:regexp) =
         let value = [ seq "commas_entry" . value_to_comma]
      in [ key k . counter "commas_entry" . spc .
           Build.opt_list value comma . eol ]

    let fw_entry (k:regexp) = [ indent . key k . spc .
	    [ key /[^ \t\r\n\/]+/ . spc . value_to_eol . eol ]]

    let send_env = array_entry /SendEnv/i

    let proxy_command = [ indent . key /ProxyCommand/i . spc . value_to_eol . eol ]

    let remote_fw = fw_entry /RemoteForward/i
    let local_fw = fw_entry /LocalForward/i

    let ciphers = commas_entry /Ciphers/i
    let macs	= commas_entry /MACs/i

    let special_entry = send_env
	                    | proxy_command
	                    | remote_fw
	                    | local_fw
	                    | macs
	                    | ciphers

    let key_re = /[A-Za-z0-9]+/
               - /SendEnv|Host|ProxyCommand|RemoteForward|LocalForward|MACs|Ciphers/i

    let other_entry = [ indent . key key_re
                    . spc . value_to_spc . eol ]

    let entry = comment | empty
              | special_entry
	            | other_entry

    let host = [ key /Host/i . spc . value_to_eol . eol . entry* ]

(************************************************************************
 * Group:                 LENS
 *************************************************************************)

    let lns = entry* . host*

    let xfm = transform lns (incl "/etc/ssh/ssh_config" .
                             incl (Sys.getenv("HOME") . "/.ssh/config"))


Enable parsing of pam

Enable parsing of pam

# Parsing /etc/inittab

map
  grammar pam
  # We really need to be able to exclude some files, like
  # backup files and .rpmsave files
  include '/etc/pam.d/*' '/system/config/pam' $basename
end

grammar pam

  token SEP /[ \t]+/ = '\t'
  token EOL '\n'
  token CONTROL /(\[[^\]]*\]|[^ \t]+)/ = 'none'
  token POUND_TO_EOL /#.*\n/ = '# '

  file: ( comment | record ) *

  comment: ( /#.*?\n/ | /[ \t]*\n/ )

  record: [ seq 'record' .
            [ label 'type' . store ... ] .
            SEP .
            [ label 'control' . store CONTROL] .
            SEP .
            [ label 'module' . store ... ] .
            ( [ SEP . label 'opts' . store ... ] )? .
            EOL
          ]
end

# Parsing /etc/inittab

#map
#  grammar pam
#  # We really need to be able to exclude some files, like
#  # backup files and .rpmsave files
#  include '/etc/pam.d/*' '/system/config/pam' $basename
#end

grammar pam

  token SEP /[ \t]+/ = '\t'
  token EOL '\n'
  token CONTROL /(\[[^\]]*\]|[^ \t]+)/ = 'none'
  token POUND_TO_EOL /#.*\n/ = '# '

  file: ( comment | record ) *

  comment: ( /#.*?\n/ | /[ \t]*\n/ )

  record: [ seq 'record' .
            [ label 'type' . store ... ] .
            SEP .
            [ label 'control' . store CONTROL] .
            SEP .
            [ label 'module' . store ... ] .
            ( [ SEP . label 'opts' . store ... ] )? .
            EOL
          ]
end


Yum: add yum-cron*.conf files

Yum: add yum-cron*.conf files


Krb5: fix parse error with IPA generated krb5.conf

module Krb5 =

autoload xfm

let comment = Inifile.comment IniFile.comment_re "#"
let empty = Inifile.empty
let eol = Inifile.eol
let dels = Util.del_str

let indent = del /[ \t]*/ ""
let comma_or_space_sep = del /[ \t,]{1,}/ " "
let eq = del /[ \t]*=[ \t]*/ " = "
let eq_openbr = del /[ \t]*=[ \t\n]*\{[ \t]*\n/ " = {\n"
let closebr = del /[ \t]*\}/ "}"

(* These two regexps for realms and apps are not entirely true
   - strictly speaking, there's no requirement that a realm is all upper case
   and an application only uses lowercase. But it's what's used in practice.

   Without that distinction we couldn't distinguish between applications
   and realms in the [appdefaults] section.
*)

let realm_re = /[A-Z][.a-zA-Z0-9-]*/
let app_re = /[a-z][a-zA-Z0-9_]*/
let name_re = /[.a-zA-Z0-9_-]+/

let value = store /[^;# \t\n{}]+/
let entry (kw:regexp) (sep:lens) (comment:lens)
    = [ indent . key kw . sep . value . (comment|eol) ] | comment

let subsec_entry (kw:regexp) (sep:lens) (comment:lens)
    = ( entry kw sep comment ) | empty

let simple_section (n:string) (k:regexp) =
  let title = Inifile.indented_title n in
  let entry = entry k eq comment in
    Inifile.record title entry

let record (t:string) (e:lens) =
  let title = Inifile.indented_title t in
    Inifile.record title e

let v4_name_convert (subsec:lens) = [ indent . key "v4_name_convert" .
                        eq_openbr .  subsec* . closebr . eol ]

(*
  For the enctypes this appears to be a list of the valid entries:
       c4-hmac arcfour-hmac aes128-cts rc4-hmac
       arcfour-hmac-md5 des3-cbc-sha1 des-cbc-md5 des-cbc-crc
*)
let enctype_re = /[a-zA-Z0-9-]{3,}/
let enctypes = /permitted_enctypes|default_tgs_enctypes|default_tkt_enctypes/i

(* An #eol label prevents ambiguity between "k = v1 v2" and "k = v1\n k = v2" *)
let enctype_list (nr:regexp) (ns:string) =
  indent . del nr ns . eq
    . Build.opt_list [ label ns . store enctype_re ] comma_or_space_sep
    . (comment|eol) . [ label "#eol" ]

let libdefaults =
  let option = entry (name_re - "v4_name_convert" - enctypes) eq comment in
  let enctype_lists = enctype_list /permitted_enctypes/i "permitted_enctypes"
                      | enctype_list /default_tgs_enctypes/i "default_tgs_enctypes"
                      | enctype_list /default_tkt_enctypes/i "default_tkt_enctypes" in
  let subsec = [ indent . key /host|plain/ . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  record "libdefaults" (option|enctype_lists|v4_name_convert subsec)

let login =
  let keys = /krb[45]_get_tickets|krb4_convert|krb_run_aklog/
    |/aklog_path|accept_passwd/ in
    simple_section "login" keys

let appdefaults =
  let option = entry (name_re - "realm" - "application") eq comment in
  let realm = [ indent . label "realm" . store realm_re .
                  eq_openbr . (option|empty)* . closebr . eol ] in
  let app = [ indent . label "application" . store app_re .
                eq_openbr . (realm|option|empty)* . closebr . eol] in
    record "appdefaults" (option|realm|app)

let realms =
  let simple_option = /kdc|admin_server|database_module|default_domain/
      |/v4_realm|auth_to_local(_names)?|master_kdc|kpasswd_server/
      |/admin_server|ticket_lifetime|pkinit_anchors/ in
  let subsec_option = /v4_instance_convert/ in
  let option = subsec_entry simple_option eq comment in
  let subsec = [ indent . key subsec_option . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  let v4subsec = [ indent . key /host|plain/ . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  let realm = [ indent . label "realm" . store realm_re .
                  eq_openbr . (option|subsec|(v4_name_convert v4subsec))* .
                  closebr . eol ] in
    record "realms" (realm|comment)

let domain_realm =
  simple_section "domain_realm" name_re

let logging =
  let keys = /kdc|admin_server|default/ in
  let xchg (m:regexp) (d:string) (l:string) =
    del m d . label l in
  let xchgs (m:string) (l:string) = xchg m m l in
  let dest =
    [ xchg /FILE[=:]/ "FILE=" "file" . value ]
    |[ xchgs "STDERR" "stderr" ]
    |[ xchgs "CONSOLE" "console" ]
    |[ xchgs "DEVICE=" "device" . value ]
    |[ xchgs "SYSLOG" "syslog" .
         ([ xchgs ":" "severity" . store /[A-Za-z0-9]+/ ].
          [ xchgs ":" "facility" . store /[A-Za-z0-9]+/ ]?)? ] in
  let entry = [ indent . key keys . eq . dest . (comment|eol) ] | comment in
    record "logging" entry

let capaths =
  let realm = [ indent . key realm_re .
                  eq_openbr .
                  (entry realm_re eq comment)* . closebr . eol ] in
    record "capaths" (realm|comment)

let dbdefaults =
  let keys = /database_module|ldap_kerberos_container_dn|ldap_kdc_dn/
    |/ldap_kadmind_dn|ldap_service_password_file|ldap_servers/
    |/ldap_conns_per_server/ in
    simple_section "dbdefaults" keys

let dbmodules =
  let keys = /db_library|ldap_kerberos_container_dn|ldap_kdc_dn/
    |/ldap_kadmind_dn|ldap_service_password_file|ldap_servers/
    |/ldap_conns_per_server/ in
    simple_section "dbmodules" keys

(* This section is not documented in the krb5.conf manpage,
   but the Fermi example uses it. *)
let instance_mapping =
  let value = dels "\"" . store /[^;# \t\n{}]*/ . dels "\"" in
  let map_node = label "mapping" . store /[a-zA-Z0-9\/*]+/ in
  let mapping = [ indent . map_node . eq .
                    [ label "value" . value ] . (comment|eol) ] in
  let instance = [ indent . key name_re .
                     eq_openbr . (mapping|comment)* . closebr . eol ] in
    record "instancemapping" instance

let kdc =
  simple_section "kdc" /profile/

let pam =
  simple_section "pam" name_re

let lns = (comment|empty)* .
  (libdefaults|login|appdefaults|realms|domain_realm
  |logging|capaths|dbdefaults|dbmodules|instance_mapping|kdc|pam)*

let xfm = transform lns (incl "/etc/krb5.conf")

module Krb5 =

autoload xfm

let comment = Inifile.comment IniFile.comment_re "#"
let empty = Inifile.empty
let eol = Inifile.eol
let dels = Util.del_str

let indent = del /[ \t]*/ ""
let comma_or_space_sep = del /[ \t,]{1,}/ " "
let eq = del /[ \t]*=[ \t]*/ " = "
let eq_openbr = del /[ \t]*=[ \t\n]*\{[ \t]*\n/ " = {\n"
let closebr = del /[ \t]*\}/ "}"

(* These two regexps for realms and apps are not entirely true
   - strictly speaking, there's no requirement that a realm is all upper case
   and an application only uses lowercase. But it's what's used in practice.

   Without that distinction we couldn't distinguish between applications
   and realms in the [appdefaults] section.
*)

let realm_re = /[A-Z][.a-zA-Z0-9-]*/
let app_re = /[a-z][a-zA-Z0-9_]*/
let name_re = /[.a-zA-Z0-9_-]+/

let value = store /[^;# \t\n{}]+/
let entry (kw:regexp) (sep:lens) (comment:lens)
    = [ indent . key kw . sep . value . (comment|eol) ] | comment

let subsec_entry (kw:regexp) (sep:lens) (comment:lens)
    = ( entry kw sep comment ) | empty

let simple_section (n:string) (k:regexp) =
  let title = Inifile.indented_title n in
  let entry = entry k eq comment in
    Inifile.record title entry

let record (t:string) (e:lens) =
  let title = Inifile.indented_title t in
    Inifile.record title e

let v4_name_convert (subsec:lens) = [ indent . key "v4_name_convert" .
                        eq_openbr .  subsec* . closebr . eol ]

(*
  For the enctypes this appears to be a list of the valid entries:
       c4-hmac arcfour-hmac aes128-cts rc4-hmac
       arcfour-hmac-md5 des3-cbc-sha1 des-cbc-md5 des-cbc-crc
*)
let enctype_re = /[a-zA-Z0-9-]{3,}/
let enctypes = /permitted_enctypes|default_tgs_enctypes|default_tkt_enctypes/i

(* An #eol label prevents ambiguity between "k = v1 v2" and "k = v1\n k = v2" *)
let enctype_list (nr:regexp) (ns:string) =
  indent . del nr ns . eq
    . Build.opt_list [ label ns . store enctype_re ] comma_or_space_sep
    . (comment|eol) . [ label "#eol" ]

let libdefaults =
  let option = entry (name_re - "v4_name_convert" - enctypes) eq comment in
  let enctype_lists = enctype_list /permitted_enctypes/i "permitted_enctypes"
                      | enctype_list /default_tgs_enctypes/i "default_tgs_enctypes"
                      | enctype_list /default_tkt_enctypes/i "default_tkt_enctypes" in
  let subsec = [ indent . key /host|plain/ . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  record "libdefaults" (option|enctype_lists|v4_name_convert subsec)

let login =
  let keys = /krb[45]_get_tickets|krb4_convert|krb_run_aklog/
    |/aklog_path|accept_passwd/ in
    simple_section "login" keys

let appdefaults =
  let option = entry (name_re - "realm" - "application") eq comment in
  let realm = [ indent . label "realm" . store realm_re .
                  eq_openbr . (option|empty)* . closebr . eol ] in
  let app = [ indent . label "application" . store app_re .
                eq_openbr . (realm|option|empty)* . closebr . eol] in
    record "appdefaults" (option|realm|app)

let realms =
  let simple_option = /kdc|admin_server|database_module|default_domain/
      |/v4_realm|auth_to_local(_names)?|master_kdc|kpasswd_server/
      |/admin_server|ticket_lifetime/ in
  let subsec_option = /v4_instance_convert/ in
  let option = subsec_entry simple_option eq comment in
  let subsec = [ indent . key subsec_option . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  let v4subsec = [ indent . key /host|plain/ . eq_openbr .
                   (subsec_entry name_re eq comment)* . closebr . eol ] in
  let realm = [ indent . label "realm" . store realm_re .
                  eq_openbr . (option|subsec|(v4_name_convert v4subsec))* .
                  closebr . eol ] in
    record "realms" (realm|comment)

let domain_realm =
  simple_section "domain_realm" name_re

let logging =
  let keys = /kdc|admin_server|default/ in
  let xchg (m:regexp) (d:string) (l:string) =
    del m d . label l in
  let xchgs (m:string) (l:string) = xchg m m l in
  let dest =
    [ xchg /FILE[=:]/ "FILE=" "file" . value ]
    |[ xchgs "STDERR" "stderr" ]
    |[ xchgs "CONSOLE" "console" ]
    |[ xchgs "DEVICE=" "device" . value ]
    |[ xchgs "SYSLOG" "syslog" .
         ([ xchgs ":" "severity" . store /[A-Za-z0-9]+/ ].
          [ xchgs ":" "facility" . store /[A-Za-z0-9]+/ ]?)? ] in
  let entry = [ indent . key keys . eq . dest . (comment|eol) ] | comment in
    record "logging" entry

let capaths =
  let realm = [ indent . key realm_re .
                  eq_openbr .
                  (entry realm_re eq comment)* . closebr . eol ] in
    record "capaths" (realm|comment)

let dbdefaults =
  let keys = /database_module|ldap_kerberos_container_dn|ldap_kdc_dn/
    |/ldap_kadmind_dn|ldap_service_password_file|ldap_servers/
    |/ldap_conns_per_server/ in
    simple_section "dbdefaults" keys

let dbmodules =
  let keys = /db_library|ldap_kerberos_container_dn|ldap_kdc_dn/
    |/ldap_kadmind_dn|ldap_service_password_file|ldap_servers/
    |/ldap_conns_per_server/ in
    simple_section "dbmodules" keys

(* This section is not documented in the krb5.conf manpage,
   but the Fermi example uses it. *)
let instance_mapping =
  let value = dels "\"" . store /[^;# \t\n{}]*/ . dels "\"" in
  let map_node = label "mapping" . store /[a-zA-Z0-9\/*]+/ in
  let mapping = [ indent . map_node . eq .
                    [ label "value" . value ] . (comment|eol) ] in
  let instance = [ indent . key name_re .
                     eq_openbr . (mapping|comment)* . closebr . eol ] in
    record "instancemapping" instance

let kdc =
  simple_section "kdc" /profile/

let pam =
  simple_section "pam" name_re

let lns = (comment|empty)* .
  (libdefaults|login|appdefaults|realms|domain_realm
  |logging|capaths|dbdefaults|dbmodules|instance_mapping|kdc|pam)*

let xfm = transform lns (incl "/etc/krb5.conf")