Add CR as a white space char in RX.space_in

CR is used in the IniFile lens as of EOL. To avoid concat ambig between
IniFile.store_to_eol and IniFile.eol, CR has to be added to Rx.space_in. This
change do not break any existing lens in the repository.

(*
Module: Rx
   Generic regexps to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Rx =

(* Group: Spaces *)
(* Variable: space
   A mandatory space or tab *)
let space     = /[ \t]+/

(* Variable: opt_space
   An optional space or tab *)
let opt_space = /[ \t]*/

(* Variable: cl
   A continued line with a backslash *)
let cl = /[ \t]*\\\\\n[ \t]*/

(* Variable: cl_or_space
   A <cl> or a <space> *)
let cl_or_space = cl | space

(* Variable: cl_or_opt_space
   A <cl> or a <opt_space> *)
let cl_or_opt_space = cl | opt_space

(* Group: General strings *)

(* Variable: space_in
   A string which not starting or ending with a space *)
let space_in  = /[^ \r\t\n].*[^ \r\t\n]|[^ \t\n\r]/

(* Variable: no_spaces
   A string with no spaces *)
let no_spaces = /[^ \t\r\n]+/

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

(* Variable: integer
   One or more digits *)
let integer    = /[0-9]+/

(* Variable: relinteger
   A relative <integer> *)
let relinteger = /[-+]?[0-9]+/

(* Variable: relinteger_noplus
   A relative <integer>, without explicit plus sign *)
let relinteger_noplus = /[-]?[0-9]+/

(* Variable: decimal
   A decimal value (using ',' or '.' as a separator) *)
let decimal    = /[0-9]+([.,][0-9]+)?/

(* Variable: reldecimal
   A relative <decimal> *)
let reldecimal    = /[+-]?[0-9]+([.,][0-9]+)?/

(* Variable: byte
  A byte (0 - 255) *)
let byte = /25[0-5]?|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]/

(* Variable: hex
   A hex value *)
let hex = /0x[0-9a-fA-F]+/

(* Variable: octal
   An octal value *)
let octal = /0[0-7]+/

(* Variable: fspath
   A filesystem path *)
let fspath    = /[^ \t\n]+/

(* Group: All but... *)
(* Variable: neg1
   Anything but a space, a comma or a comment sign *)
let neg1      = /[^,# \n\t]+/

(*
 * Group: IPs
 * Cf. http://blog.mes-stats.fr/2008/10/09/regex-ipv4-et-ipv6/ (in fr)
 *)

(* Variable: ipv4 *)
let ipv4 =
  let dot     = "." in
    byte . dot . byte . dot . byte . dot . byte

(* Variable: ipv6 *)
let ipv6 =
  /(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})/
  | (    /([0-9A-Fa-f]{1,4}:){6}/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /([0-9A-Fa-f]{1,4}:){0,5}:/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /::([0-9A-Fa-f]{1,4}:){0,5}/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /[0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}/
           . /[0-9A-Fa-f]{1,4}/
    )
  | /(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){1,7}:)/

(* Variable: ip
   An <ipv4> or <ipv6> *)
let ip        = ipv4 | ipv6

(* Variable: hostname
   A valid RFC 1123 hostname *)
let hostname = /(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*(
                  [A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])/

(*
 * Variable: device_name
 * A Linux device name like eth0 or i2c-0. Might still be too restrictive
 *)

let device_name = /[a-zA-Z0-9_?.+:!-]+/

(*
 * Variable: email_addr
 *    To be refined
 *)
let email_addr = /[A-Za-z0-9_+.-]+@[A-Za-z0-9_.-]+/

(*
Module: Rx
   Generic regexps to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Rx =

(* Group: Spaces *)
(* Variable: space
   A mandatory space or tab *)
let space     = /[ \t]+/

(* Variable: opt_space
   An optional space or tab *)
let opt_space = /[ \t]*/

(* Variable: cl
   A continued line with a backslash *)
let cl = /[ \t]*\\\\\n[ \t]*/

(* Variable: cl_or_space
   A <cl> or a <space> *)
let cl_or_space = cl | space

(* Variable: cl_or_opt_space
   A <cl> or a <opt_space> *)
let cl_or_opt_space = cl | opt_space

(* Group: General strings *)

(* Variable: space_in
   A string which not starting or ending with a space *)
let space_in  = /[^ \t\n].*[^ \t\n]|[^ \t\n]/

(* Variable: no_spaces
   A string with no spaces *)
let no_spaces = /[^ \t\r\n]+/

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

(* Variable: integer
   One or more digits *)
let integer    = /[0-9]+/

(* Variable: relinteger
   A relative <integer> *)
let relinteger = /[-+]?[0-9]+/

(* Variable: relinteger_noplus
   A relative <integer>, without explicit plus sign *)
let relinteger_noplus = /[-]?[0-9]+/

(* Variable: decimal
   A decimal value (using ',' or '.' as a separator) *)
let decimal    = /[0-9]+([.,][0-9]+)?/

(* Variable: reldecimal
   A relative <decimal> *)
let reldecimal    = /[+-]?[0-9]+([.,][0-9]+)?/

(* Variable: byte
  A byte (0 - 255) *)
let byte = /25[0-5]?|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]/

(* Variable: hex
   A hex value *)
let hex = /0x[0-9a-fA-F]+/

(* Variable: octal
   An octal value *)
let octal = /0[0-7]+/

(* Variable: fspath
   A filesystem path *)
let fspath    = /[^ \t\n]+/

(* Group: All but... *)
(* Variable: neg1
   Anything but a space, a comma or a comment sign *)
let neg1      = /[^,# \n\t]+/

(*
 * Group: IPs
 * Cf. http://blog.mes-stats.fr/2008/10/09/regex-ipv4-et-ipv6/ (in fr)
 *)

(* Variable: ipv4 *)
let ipv4 =
  let dot     = "." in
    byte . dot . byte . dot . byte . dot . byte

(* Variable: ipv6 *)
let ipv6 =
  /(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})/
  | (    /([0-9A-Fa-f]{1,4}:){6}/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /([0-9A-Fa-f]{1,4}:){0,5}:/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /::([0-9A-Fa-f]{1,4}:){0,5}/
           . /((((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))\.){3}/
           . /(((25[0-5])|(1[0-9]{2})|(2[0-4][0-9])|([0-9]{1,2})))/
    )
  | (    /[0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}/
           . /[0-9A-Fa-f]{1,4}/
    )
  | /(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})/
  | /(([0-9A-Fa-f]{1,4}:){1,7}:)/

(* Variable: ip
   An <ipv4> or <ipv6> *)
let ip        = ipv4 | ipv6

(* Variable: hostname
   A valid RFC 1123 hostname *)
let hostname = /(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*(
                  [A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])/

(*
 * Variable: device_name
 * A Linux device name like eth0 or i2c-0. Might still be too restrictive
 *)

let device_name = /[a-zA-Z0-9_?.+:!-]+/

(*
 * Variable: email_addr
 *    To be refined
 *)
let email_addr = /[A-Za-z0-9_+.-]+@[A-Za-z0-9_.-]+/


Dhcpd: add Red Hat's dhcpd.conf locations

RHEL 5 uses /etc/dhcpd.conf.
RHEL 6 uses /etc/dhcp/dhcpd.conf.

Signed-off-by: Michael Chapman <a17fed27eaa842282862ff7c1b9c8395a26ac320@very.puzzling.org>

(*
Module: Dhcpd
  BIND dhcp 3 server configuration module for Augeas

Author: Francis Giraldeau <francis.giraldeau@usherbrooke.ca>

About: Reference
  Reference: manual of dhcpd.conf and dhcp-eval
  Follow dhclient module for tree structure

About: License
    This file is licensed under the GPL.

About: Lens Usage
  Sample usage of this lens in augtool

  Directive without argument.
  Set this dhcpd server authoritative on the domain.
  > clear /files/etc/dhcp3/dhcpd.conf/authoritative

  Directives with integer or string argument.
  Set max-lease-time to one hour:
  > set /files/etc/dhcp3/dhcpd.conf/max-lease-time 3600

  Options are declared as a list, even for single values.
  Set the domain of the network:
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name/arg example.org
  Set two name server:
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[1] foo.example.org
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[2] bar.example.org

  Create the subnet 172.16.0.1 with 10 addresses:
  > clear /files/etc/dhcp3/dhcpd.conf/subnet[last() + 1]
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/network 172.16.0.0
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/netmask 255.255.255.0
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/from 172.16.0.10
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/to 172.16.0.20

  Create a new group "foo" with one static host. Nodes type and address are ordered.
  > ins group after /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/*[last()]
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[last()]/host foo
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/type "ethernet"
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/address "00:00:00:aa:bb:cc"
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/fixed-address 172.16.0.100

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

module Dhcpd =

autoload xfm

(************************************************************************
 *                           USEFUL PRIMITIVES
 *************************************************************************)
let dels (s:string)   = del s s
let eol               = Util.eol
let comment           = Util.comment
let empty             = Util.empty
let indent            = Util.indent
let eos               = comment?

(* Define separators *)
let sep_spc           = del /[ \t]+/ " "
let sep_osp           = del /[ \t]*/ ""
let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
let sep_obr           = del /[ \t]*\{([ \t]*\n)*/ " {\n"
let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
let sep_slh           = del "\/" "/"
let sep_col           = del ":" ":"
let sep_eq            = del /[ \t]*=[ \t]*/ "="
let scl               = del ";" ";"

(* Define basic types *)
let word              = /[A-Za-z0-9_.-]+(\[[0-9]+\])?/
let ip                = Rx.ipv4

(* Define fields *)

(* borrowed from sysconfig.aug *)
  (* Chars allowed in a bare string *)
  let bchar = /[^ \t\n"'\\{}#,()]|\\\\./
  let qchar = /["']/  (* " *)

  (* We split the handling of right hand sides into a few cases:
   *   bare  - strings that contain no spaces, optionally enclosed in
   *           single or double quotes
   *   dquot - strings that contain at least one space or apostrophe,
   *           which must be enclosed in double quotes
   *   squot - strings that contain an unescaped double quote
   *)
  let bare = del qchar? "" . store (bchar+) . del qchar? ""
  let dquot =
    del qchar "\"" . store (bchar* . /[ \t']/ . bchar*)+ . del qchar "\""
  let squot =
    dels "'" . store ((bchar|/[ \t]/)* . "\"" . (bchar|/[ \t]/)*)+ . dels "'"

let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/

let sto_number        = store /[0-9][0-9]*/

(************************************************************************
 *                         NO ARG STATEMENTS
 *************************************************************************)

let stmt_noarg_re     =   "authoritative"
                        | "primary"
                        | "secondary"

let stmt_noarg        = [ indent
                        . key stmt_noarg_re
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         INT ARG STATEMENTS
 *************************************************************************)

let stmt_integer_re   = "default-lease-time"
                      | "max-lease-time"
                      | "min-lease-time"
                      | /lease[ ]+limit/
                      | "port"
                      | /peer[ ]+port/
                      | "max-response-delay"
                      | "max-unacked-updates"
                      | "mclt"
                      | "split"
                      | /load[ ]+balance[ ]+max[ ]+seconds/
                      | "max-lease-misbalance"
                      | "max-lease-ownership"
                      | "min-balance"
                      | "max-balance"
                      | "adaptive-lease-time-threshold"
                      | "dynamic-bootp-lease-length"
                      | "local-port"
                      | "min-sec"
                      | "omapi-port"
                      | "ping-timeout"
                      | "remote-port"

let stmt_integer      = [ indent
                        . key stmt_integer_re
                        . sep_spc
                        . sto_number
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         STRING ARG STATEMENTS
 *************************************************************************)

let stmt_string_re    = "ddns-update-style"
                      | "ddns-updates"
                      | "ddns-hostname"
                      | "ddns-domainname"
                      | "ddns-rev-domainname"
                      | "log-facility"
                      | "filename"
                      | "server-name"
                      | "fixed-address"
                      | /failover[ ]+peer/
                      | "use-host-decl-names"
                      | "next-server"
                      | "address"
                      | /peer[ ]+address/
                      | "type"
                      | "file"
                      | "algorithm"
                      | "secret"
                      | "key"
                      | "include"
                      | "hba"
                      | "boot-unknown-clients"
                      | "db-time-format"
                      | "do-forward-updates"
                      | "dynamic-bootp-lease-cutoff"
                      | "get-lease-hostnames"
                      | "infinite-is-reserved"
                      | "lease-file-name"
                      | "local-address"
                      | "one-lease-per-client"
                      | "pid-file-name"
                      | "ping-check"
                      | "server-identifier"
                      | "site-option-space"
                      | "stash-agent-options"
                      | "update-conflict-detection"
                      | "update-optimization"
                      | "update-static-leases"
                      | "use-host-decl-names"
                      | "use-lease-addr-for-default-route"
                      | "vendor-option-space"

let stmt_string_tpl (l:lens) = [ indent
                        . key stmt_string_re
                        . sep_spc
                        . l
                        . sep_scl
                        . eos ]

let stmt_string  = stmt_string_tpl bare |stmt_string_tpl squot | stmt_string_tpl dquot

(************************************************************************
 *                         RANGE STATEMENTS
 *************************************************************************)

let stmt_range        = [ indent
                        . key "range"
                        . sep_spc
                        . [ label "flag" . store /dynamic-bootp/ . sep_spc ]?
                        . [ label "from" . store ip . sep_spc ]?
                        . [ label "to" . store ip ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         HARDWARE STATEMENTS
 *************************************************************************)

let stmt_hardware     = [ indent
                        . key "hardware"
                        . sep_spc
                        . [ label "type" . store /ethernet|tokenring/ ]
                        . sep_spc
                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         OPTION STATEMENTS
 *************************************************************************)
(* The general case is considering options as a list *)

let stmt_option_code  = [ label "label" . store word . sep_spc ]
                        . [ key "code" . sep_spc . store word ]
                        . sep_eq
                        . [ label "type" . store word ]

let stmt_option_list  = ([ label "arg" . bare ] | [ label "arg" . dquot ] | [ label "arg" . squot ])
                        . ( sep_com . ([ label "arg" . bare ] | [ label "arg" . dquot ] | [ label "arg" . squot ]))*

let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]

let stmt_option_body = stmt_option_basic | stmt_option_extra

let stmt_option1  = [ indent
                        . key "option"
                        . sep_spc
                        . stmt_option_body
                        . sep_scl
                        . eos ]

let stmt_option2  = [ indent
                        . dels "option" . label "rfc-code"
                        . sep_spc
                        . stmt_option_code
                        . sep_scl
                        . eos ]

let stmt_option = stmt_option1 | stmt_option2

(************************************************************************
 *                         SUBCLASS STATEMENTS
 *************************************************************************)
(* this statement is not well documented in the manual dhcpd.conf
   we support basic use case *)

let stmt_subclass = [ indent . key "subclass" . sep_spc .
                      ([ label "name" . dquot ]|
                       [ label "name" . squot ]|
                       [ label "name" . bare ]) . sep_spc .
                       [ label "value" . bare ] . sep_scl . eos ]

(************************************************************************
 *                         ALLOW/DENY STATEMENTS
 *************************************************************************)
(* We have to use special key for allow/deny members of
  to avoid ambiguity in the put direction *)

let allow_deny_re     = "unknown-clients"
                      | /dynamic[ ]+bootp[ ]+clients/
                      | /authenticated[ ]+clients/
                      | /unauthenticated[ ]+clients/

let stmt_secu_re      = "allow"
                      | "deny"

let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"

let stmt_secu_tpl (l:lens) (s:string) =
                  [ indent . l . sep_spc . label s . bare . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . squot . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . dquot . sep_scl . eos ]

let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
                          store allow_deny_re . sep_scl . eos ] |
                        stmt_secu_tpl del_allow "allow-members-of" |
                        stmt_secu_tpl del_deny "deny-members-of"

(************************************************************************
 *                         MATCH STATEMENTS
 *************************************************************************)

let sto_fct = store (word . /[ \t]*\([^)]*\)/)
let sto_option = store (/option[ ]+/ . word)
let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
let fct_re = "substring" | "binary-to-ascii"

let fct_args = [ label "args" . dels "(" . sep_osp .
                 ([ label "arg" . store sto_com ] . [ label "arg" . sep_com . store sto_com ]+) .
                        sep_osp . dels ")" ]

let stmt_match_if = [ dels "if" . sep_spc . store fct_re . sep_osp . label "function" . fct_args ] .
                      sep_eq . ([ label "value" . bare ]|[ label "value" . squot ]|[ label "value" . dquot ])

let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
                       dels "(" . sep_osp .
                       [ label "args" .
                         [ label "arg" . store sto_com ] .
                         [ sep_com . label "arg" . store sto_com ]+ ] .
                       dels ")" ]

let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]

let stmt_match = stmt_match_tpl (stmt_match_if | stmt_match_pfv )

(************************************************************************
 *                         BLOCK STATEMENTS
 *************************************************************************)
(* Blocks doesn't support comments at the end of the closing bracket *)

let stmt_entry        =   stmt_secu
                        | stmt_option
                        | stmt_hardware
                        | stmt_range
                        | stmt_string
                        | stmt_integer
                        | stmt_noarg
                        | stmt_match
                        | stmt_subclass
                        | empty
                        | comment

let stmt_block_noarg_re = "pool"
                        | "group"
                        | "allow-update"

let stmt_block_noarg (body:lens)
                        = [ indent
                        . key stmt_block_noarg_re
                        . sep_obr
                        . body*
                        . sep_cbr ]

let stmt_block_arg_re = "host"
                      | "class"
                      | "shared-network"
                      | /failover[ ]+peer/
                      | "zone"
                      | "key"

let stmt_block_arg (body:lens)
                      = [ indent
                        . key stmt_block_arg_re
                        . sep_spc
                        . sto_to_spc
                        . sep_obr
                        . body*
                        . sep_cbr ]

let stmt_block_subnet (body:lens)
                      = [ indent
                        . key "subnet"
                        . sep_spc
                        . [ label "network" . store ip ]
                        . sep_spc
                        . [ key "netmask" . sep_spc . store ip ]
                        . sep_obr
                        . body*
                        . sep_cbr ]

let all_block (body:lens) =
    let lns1 = stmt_block_subnet body in
    let lns2 = stmt_block_arg body in
    let lns3 = stmt_block_noarg body in
    (lns1 | lns2 | lns3 | stmt_entry)

let rec lns_staging = stmt_entry|all_block lns_staging
let lns = (lns_staging)*

let filter = incl "/etc/dhcp3/dhcpd.conf"
           . incl "/etc/dhcp/dhcpd.conf"
           . incl "/etc/dhcpd.conf"
           . Util.stdexcl

let xfm = transform lns filter

(*
Module: Dhcpd
  BIND dhcp 3 server configuration module for Augeas

Author: Francis Giraldeau <francis.giraldeau@usherbrooke.ca>

About: Reference
  Reference: manual of dhcpd.conf and dhcp-eval
  Follow dhclient module for tree structure

About: License
    This file is licensed under the GPL.

About: Lens Usage
  Sample usage of this lens in augtool

  Directive without argument.
  Set this dhcpd server authoritative on the domain.
  > clear /files/etc/dhcp3/dhcpd.conf/authoritative

  Directives with integer or string argument.
  Set max-lease-time to one hour:
  > set /files/etc/dhcp3/dhcpd.conf/max-lease-time 3600

  Options are declared as a list, even for single values.
  Set the domain of the network:
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name/arg example.org
  Set two name server:
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[1] foo.example.org
  > set /files/etc/dhcp3/dhcpd.conf/option/domain-name-servers/arg[2] bar.example.org

  Create the subnet 172.16.0.1 with 10 addresses:
  > clear /files/etc/dhcp3/dhcpd.conf/subnet[last() + 1]
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/network 172.16.0.0
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/netmask 255.255.255.0
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/from 172.16.0.10
  > set /files/etc/dhcp3/dhcpd.conf/subnet[last()]/range/to 172.16.0.20

  Create a new group "foo" with one static host. Nodes type and address are ordered.
  > ins group after /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/*[last()]
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[last()]/host foo
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/type "ethernet"
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/hardware/address "00:00:00:aa:bb:cc"
  > set /files/etc/dhcp3/dhcpd.conf/subnet[network='172.16.0.0']/group[host='foo']/host/fixed-address 172.16.0.100

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

module Dhcpd =

autoload xfm

(************************************************************************
 *                           USEFUL PRIMITIVES
 *************************************************************************)
let dels (s:string)   = del s s
let eol               = Util.eol
let comment           = Util.comment
let empty             = Util.empty
let indent            = Util.indent
let eos               = comment?

(* Define separators *)
let sep_spc           = del /[ \t]+/ " "
let sep_osp           = del /[ \t]*/ ""
let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
let sep_obr           = del /[ \t]*\{([ \t]*\n)*/ " {\n"
let sep_cbr           = del /[ \t]*\}([ \t]*\n)*/ "}\n"
let sep_com           = del /[ \t\n]*,[ \t\n]*/ ", "
let sep_slh           = del "\/" "/"
let sep_col           = del ":" ":"
let sep_eq            = del /[ \t]*=[ \t]*/ "="
let scl               = del ";" ";"

(* Define basic types *)
let word              = /[A-Za-z0-9_.-]+(\[[0-9]+\])?/
let ip                = Rx.ipv4

(* Define fields *)

(* borrowed from sysconfig.aug *)
  (* Chars allowed in a bare string *)
  let bchar = /[^ \t\n"'\\{}#,()]|\\\\./
  let qchar = /["']/  (* " *)

  (* We split the handling of right hand sides into a few cases:
   *   bare  - strings that contain no spaces, optionally enclosed in
   *           single or double quotes
   *   dquot - strings that contain at least one space or apostrophe,
   *           which must be enclosed in double quotes
   *   squot - strings that contain an unescaped double quote
   *)
  let bare = del qchar? "" . store (bchar+) . del qchar? ""
  let dquot =
    del qchar "\"" . store (bchar* . /[ \t']/ . bchar*)+ . del qchar "\""
  let squot =
    dels "'" . store ((bchar|/[ \t]/)* . "\"" . (bchar|/[ \t]/)*)+ . dels "'"

let sto_to_spc        = store /[^\\#,;\{\}" \t\n]+|"[^\\#"\n]+"/
let sto_to_scl        = store /[^ \t;][^;\n=]+[^ \t;]|[^ \t;=]+/

let sto_number        = store /[0-9][0-9]*/

(************************************************************************
 *                         NO ARG STATEMENTS
 *************************************************************************)

let stmt_noarg_re     =   "authoritative"
                        | "primary"
                        | "secondary"

let stmt_noarg        = [ indent
                        . key stmt_noarg_re
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         INT ARG STATEMENTS
 *************************************************************************)

let stmt_integer_re   = "default-lease-time"
                      | "max-lease-time"
                      | "min-lease-time"
                      | /lease[ ]+limit/
                      | "port"
                      | /peer[ ]+port/
                      | "max-response-delay"
                      | "max-unacked-updates"
                      | "mclt"
                      | "split"
                      | /load[ ]+balance[ ]+max[ ]+seconds/
                      | "max-lease-misbalance"
                      | "max-lease-ownership"
                      | "min-balance"
                      | "max-balance"
                      | "adaptive-lease-time-threshold"
                      | "dynamic-bootp-lease-length"
                      | "local-port"
                      | "min-sec"
                      | "omapi-port"
                      | "ping-timeout"
                      | "remote-port"

let stmt_integer      = [ indent
                        . key stmt_integer_re
                        . sep_spc
                        . sto_number
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         STRING ARG STATEMENTS
 *************************************************************************)

let stmt_string_re    = "ddns-update-style"
                      | "ddns-updates"
                      | "ddns-hostname"
                      | "ddns-domainname"
                      | "ddns-rev-domainname"
                      | "log-facility"
                      | "filename"
                      | "server-name"
                      | "fixed-address"
                      | /failover[ ]+peer/
                      | "use-host-decl-names"
                      | "next-server"
                      | "address"
                      | /peer[ ]+address/
                      | "type"
                      | "file"
                      | "algorithm"
                      | "secret"
                      | "key"
                      | "include"
                      | "hba"
                      | "boot-unknown-clients"
                      | "db-time-format"
                      | "do-forward-updates"
                      | "dynamic-bootp-lease-cutoff"
                      | "get-lease-hostnames"
                      | "infinite-is-reserved"
                      | "lease-file-name"
                      | "local-address"
                      | "one-lease-per-client"
                      | "pid-file-name"
                      | "ping-check"
                      | "server-identifier"
                      | "site-option-space"
                      | "stash-agent-options"
                      | "update-conflict-detection"
                      | "update-optimization"
                      | "update-static-leases"
                      | "use-host-decl-names"
                      | "use-lease-addr-for-default-route"
                      | "vendor-option-space"

let stmt_string_tpl (l:lens) = [ indent
                        . key stmt_string_re
                        . sep_spc
                        . l
                        . sep_scl
                        . eos ]

let stmt_string  = stmt_string_tpl bare |stmt_string_tpl squot | stmt_string_tpl dquot

(************************************************************************
 *                         RANGE STATEMENTS
 *************************************************************************)

let stmt_range        = [ indent
                        . key "range"
                        . sep_spc
                        . [ label "flag" . store /dynamic-bootp/ . sep_spc ]?
                        . [ label "from" . store ip . sep_spc ]?
                        . [ label "to" . store ip ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         HARDWARE STATEMENTS
 *************************************************************************)

let stmt_hardware     = [ indent
                        . key "hardware"
                        . sep_spc
                        . [ label "type" . store /ethernet|tokenring/ ]
                        . sep_spc
                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         OPTION STATEMENTS
 *************************************************************************)
(* The general case is considering options as a list *)

let stmt_option_code  = [ label "label" . store word . sep_spc ]
                        . [ key "code" . sep_spc . store word ]
                        . sep_eq
                        . [ label "type" . store word ]

let stmt_option_list  = ([ label "arg" . bare ] | [ label "arg" . dquot ] | [ label "arg" . squot ])
                        . ( sep_com . ([ label "arg" . bare ] | [ label "arg" . dquot ] | [ label "arg" . squot ]))*

let stmt_option_basic = [ key word . sep_spc . stmt_option_list ]
let stmt_option_extra = [ key word . sep_spc . store /true|false/ . sep_spc . stmt_option_list ]

let stmt_option_body = stmt_option_basic | stmt_option_extra

let stmt_option1  = [ indent
                        . key "option"
                        . sep_spc
                        . stmt_option_body
                        . sep_scl
                        . eos ]

let stmt_option2  = [ indent
                        . dels "option" . label "rfc-code"
                        . sep_spc
                        . stmt_option_code
                        . sep_scl
                        . eos ]

let stmt_option = stmt_option1 | stmt_option2

(************************************************************************
 *                         SUBCLASS STATEMENTS
 *************************************************************************)
(* this statement is not well documented in the manual dhcpd.conf
   we support basic use case *)

let stmt_subclass = [ indent . key "subclass" . sep_spc .
                      ([ label "name" . dquot ]|
                       [ label "name" . squot ]|
                       [ label "name" . bare ]) . sep_spc .
                       [ label "value" . bare ] . sep_scl . eos ]

(************************************************************************
 *                         ALLOW/DENY STATEMENTS
 *************************************************************************)
(* We have to use special key for allow/deny members of
  to avoid ambiguity in the put direction *)

let allow_deny_re     = "unknown-clients"
                      | /dynamic[ ]+bootp[ ]+clients/
                      | /authenticated[ ]+clients/
                      | /unauthenticated[ ]+clients/

let stmt_secu_re      = "allow"
                      | "deny"

let del_allow = del /allow[ ]+members[ ]+of/ "allow members of"
let del_deny  = del /deny[ \t]+members[ \t]+of/ "deny members of"

let stmt_secu_tpl (l:lens) (s:string) =
                  [ indent . l . sep_spc . label s . bare . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . squot . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . dquot . sep_scl . eos ]

let stmt_secu         = [ indent . key stmt_secu_re . sep_spc .
                          store allow_deny_re . sep_scl . eos ] |
                        stmt_secu_tpl del_allow "allow-members-of" |
                        stmt_secu_tpl del_deny "deny-members-of"

(************************************************************************
 *                         MATCH STATEMENTS
 *************************************************************************)

let sto_fct = store (word . /[ \t]*\([^)]*\)/)
let sto_option = store (/option[ ]+/ . word)
let sto_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
let fct_re = "substring" | "binary-to-ascii"

let fct_args = [ label "args" . dels "(" . sep_osp .
                 ([ label "arg" . store sto_com ] . [ label "arg" . sep_com . store sto_com ]+) .
                        sep_osp . dels ")" ]

let stmt_match_if = [ dels "if" . sep_spc . store fct_re . sep_osp . label "function" . fct_args ] .
                      sep_eq . ([ label "value" . bare ]|[ label "value" . squot ]|[ label "value" . dquot ])

let stmt_match_pfv = [ label "function" . store "pick-first-value" . sep_spc .
                       dels "(" . sep_osp .
                       [ label "args" .
                         [ label "arg" . store sto_com ] .
                         [ sep_com . label "arg" . store sto_com ]+ ] .
                       dels ")" ]

let stmt_match_tpl (l:lens) = [ indent . key "match" . sep_spc . l . sep_scl . eos ]

let stmt_match = stmt_match_tpl (stmt_match_if | stmt_match_pfv )

(************************************************************************
 *                         BLOCK STATEMENTS
 *************************************************************************)
(* Blocks doesn't support comments at the end of the closing bracket *)

let stmt_entry        =   stmt_secu
                        | stmt_option
                        | stmt_hardware
                        | stmt_range
                        | stmt_string
                        | stmt_integer
                        | stmt_noarg
                        | stmt_match
                        | stmt_subclass
                        | empty
                        | comment

let stmt_block_noarg_re = "pool"
                        | "group"
                        | "allow-update"

let stmt_block_noarg (body:lens)
                        = [ indent
                        . key stmt_block_noarg_re
                        . sep_obr
                        . body*
                        . sep_cbr ]

let stmt_block_arg_re = "host"
                      | "class"
                      | "shared-network"
                      | /failover[ ]+peer/
                      | "zone"
                      | "key"

let stmt_block_arg (body:lens)
                      = [ indent
                        . key stmt_block_arg_re
                        . sep_spc
                        . sto_to_spc
                        . sep_obr
                        . body*
                        . sep_cbr ]

let stmt_block_subnet (body:lens)
                      = [ indent
                        . key "subnet"
                        . sep_spc
                        . [ label "network" . store ip ]
                        . sep_spc
                        . [ key "netmask" . sep_spc . store ip ]
                        . sep_obr
                        . body*
                        . sep_cbr ]

let all_block (body:lens) =
    let lns1 = stmt_block_subnet body in
    let lns2 = stmt_block_arg body in
    let lns3 = stmt_block_noarg body in
    (lns1 | lns2 | lns3 | stmt_entry)

let rec lns_staging = stmt_entry|all_block lns_staging
let lns = (lns_staging)*

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


Sysctl: exclude /etc/sysctl.d/README.sysctl

(*
Module: Sysctl
  Parses /etc/sysctl.conf and /etc/sysctl.d/*

Author: David Lutterkort <lutter@redhat.com>

About: Reference

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/sysctl.conf and /etc/sysctl.d/*. See <filter>.

About: Examples
   The <Test_Sysctl> file contains various examples and tests.
*)

module Sysctl =
autoload xfm

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

(* View: comment *)
let comment = Util.comment_generic /[ \t]*[#;][ \t]*/ "# "

(* View: lns
     The sysctl lens *)
let lns = (Util.empty | comment | Simplevars.entry)*

let xfm = transform lns filter

(*
Module: Sysctl
  Parses /etc/sysctl.conf and /etc/sysctl.d/*

Author: David Lutterkort <lutter@redhat.com>

About: Reference

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/sysctl.conf and /etc/sysctl.d/*. See <filter>.

About: Examples
   The <Test_Sysctl> file contains various examples and tests.
*)

module Sysctl =
autoload xfm

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

(* View: comment *)
let comment = Util.comment_generic /[ \t]*[#;][ \t]*/ "# "

(* View: lns
     The sysctl lens *)
let lns = (Util.empty | comment | Simplevars.entry)*

let xfm = transform lns filter


Parse available sites for nginx (#471)

Parse available sites for nginx (#471)

Some of the configuration for nginx at least for Debian & Ubuntu can be found at `/etc/nginx/sites-available/`. These files are specific to each server nginx is going to proxy.

(* Module: Nginx
   Nginx module for Augeas

Authors: Ian Berry <iberry@barracuda.com>
         Raphael Pinson <raphael.pinson@camptocamp.com>

About: Reference

   This module was built to support a limited subset of nginx
   configuration syntax. It works fine with simple blocks and
   field/value lines.

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

About: Lens Usage
   To be documented

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

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

About: TODO
   * Convert statement keyworks for a regex
   * Support more advanced block syntax (location)
*)

module Nginx =

autoload xfm

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

(* Variable: block_re
     The keywords reserved for block entries *)
let block_re = "http" | "events" | "server" | "mail" | "stream"

(* All block keywords, including the ones we treat specially *)
let block_re_all = block_re | "if" | "location" | "geo" | "map"
  | "split_clients" | "upstream"

(* View: simple
     A simple entry *)
let simple =
     let kw = word - block_re_all
  in let mask = [ label "mask" . Util.del_str "/" . store Rx.integer ]
  in let sto = store /[^ \t\n;][^;]*/ . Sep.semicolon
  in [ Util.indent . key kw . mask? . Sep.space . sto . (Util.eol|Util.comment_eol) ]

(* View: server
     A simple server entry *)
let server =
  [ Util.indent . label "@server" . Util.del_str "server"
  . [ Sep.space . label "@address" . store word ]
  . [ Sep.space . key word . (Sep.equal . store word)? ]*
  . Sep.semicolon
  . (Util.eol|Util.comment_eol) ]

let arg (name:string) (rx:regexp) =
  [ label name . Sep.space . store rx ]

(* Match any argument (as much as possible) *)
let any_rx =
  let bare_rx = /[^" \t\n{][^ \t\n{]*/ in
  let dquote_rx = /"([^\"]|\\.)*"/ in
  bare_rx | dquote_rx

let any_arg (name:string) = arg name any_rx

(* 'if' conditions are enclosed in matching parens which we can't match
   precisely with a regular expression. Instead, we gobble up anything that
   doesn't contain an opening brace. That can of course lead to trouble if
   a condition actually contains an opening brace *)
let block_if = key "if"
             . arg "#cond" /\(([^ \t\n{]|[ \t\n][^{])*\)/

let block_location = key "location"
  . (arg "#comp" /=|~|~\*|\^~/)?
  . any_arg "#uri"

let block_geo = key "geo"
  . (any_arg "#address")?
  . any_arg "#geo"

let block_map = key "map"
  . any_arg "#source"
  . any_arg "#variable"

let block_split_clients = key "split_clients"
  . any_arg "#string"
  . any_arg "#variable"

let block_upstream = key "upstream"
  . any_arg "#name"

let block_head = key block_re
  | block_if
  | block_location
  | block_geo
  | block_map
  | block_split_clients
  | block_upstream

(* View: block
     A block containing <simple> entries *)
let block (entry : lens) =
  [ Util.indent . block_head
  . Build.block_newlines entry Util.comment
  . Util.eol ]

let rec directive = simple | server | block directive

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

(* Variable: filter *)
let filter = incl "/etc/nginx/nginx.conf"
           . incl "/etc/nginx/conf.d/*.conf"
           . incl "/etc/nginx/sites-available/*"
           . incl "/usr/portage/www-servers/nginx/files/nginx.conf"
           . incl "/usr/local/etc/nginx/nginx.conf"
           . incl "/usr/local/etc/nginx/conf.d/*.conf"
           . incl "/usr/local/etc/nginx/sites-available/*"

let xfm = transform lns filter

(* Module: Nginx
   Nginx module for Augeas

Authors: Ian Berry <iberry@barracuda.com>
         Raphael Pinson <raphael.pinson@camptocamp.com>

About: Reference

   This module was built to support a limited subset of nginx
   configuration syntax. It works fine with simple blocks and
   field/value lines.

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

About: Lens Usage
   To be documented

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

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

About: TODO
   * Convert statement keyworks for a regex
   * Support more advanced block syntax (location)
*)

module Nginx =

autoload xfm

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

(* Variable: block_re
     The keywords reserved for block entries *)
let block_re = "http" | "events" | "server" | "mail" | "stream"

(* All block keywords, including the ones we treat specially *)
let block_re_all = block_re | "if" | "location" | "geo" | "map"
  | "split_clients" | "upstream"

(* View: simple
     A simple entry *)
let simple =
     let kw = word - block_re_all
  in let mask = [ label "mask" . Util.del_str "/" . store Rx.integer ]
  in let sto = store /[^ \t\n;][^;]*/ . Sep.semicolon
  in [ Util.indent . key kw . mask? . Sep.space . sto . (Util.eol|Util.comment_eol) ]

(* View: server
     A simple server entry *)
let server =
  [ Util.indent . label "@server" . Util.del_str "server"
  . [ Sep.space . label "@address" . store word ]
  . [ Sep.space . key word . (Sep.equal . store word)? ]*
  . Sep.semicolon
  . (Util.eol|Util.comment_eol) ]

let arg (name:string) (rx:regexp) =
  [ label name . Sep.space . store rx ]

(* Match any argument (as much as possible) *)
let any_rx =
  let bare_rx = /[^" \t\n{][^ \t\n{]*/ in
  let dquote_rx = /"([^\"]|\\.)*"/ in
  bare_rx | dquote_rx

let any_arg (name:string) = arg name any_rx

(* 'if' conditions are enclosed in matching parens which we can't match
   precisely with a regular expression. Instead, we gobble up anything that
   doesn't contain an opening brace. That can of course lead to trouble if
   a condition actually contains an opening brace *)
let block_if = key "if"
             . arg "#cond" /\(([^ \t\n{]|[ \t\n][^{])*\)/

let block_location = key "location"
  . (arg "#comp" /=|~|~\*|\^~/)?
  . any_arg "#uri"

let block_geo = key "geo"
  . (any_arg "#address")?
  . any_arg "#geo"

let block_map = key "map"
  . any_arg "#source"
  . any_arg "#variable"

let block_split_clients = key "split_clients"
  . any_arg "#string"
  . any_arg "#variable"

let block_upstream = key "upstream"
  . any_arg "#name"

let block_head = key block_re
  | block_if
  | block_location
  | block_geo
  | block_map
  | block_split_clients
  | block_upstream

(* View: block
     A block containing <simple> entries *)
let block (entry : lens) =
  [ Util.indent . block_head
  . Build.block_newlines entry Util.comment
  . Util.eol ]

let rec directive = simple | server | block directive

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

(* Variable: filter *)
let filter = incl "/etc/nginx/nginx.conf"
           . incl "/etc/nginx/conf.d/*.conf"
           . incl "/usr/portage/www-servers/nginx/files/nginx.conf"
           . incl "/usr/local/etc/nginx/nginx.conf"
           . incl "/usr/local/etc/nginx/conf.d/*.conf"

let xfm = transform lns filter


* lenses/tests/test_sysconfig.aug: use a variable 'lns' for Shellvars.lns

(* 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"   = "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#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" = "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" = "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" = "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" = "${!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" }

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

(* Test for shell lens *)
module Test_shellvars =

  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 Shellvars.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"   = "ONBOOT"
        { "#comment" = "We do not want this var" } }

  test Shellvars.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 Shellvars.lns get empty_val =
    { "EMPTY" = "" } { "DEVICE" = "eth0" }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  test Shellvars.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 Shellvars.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 Shellvars.lns get "export FOO\n" =
  { "@export" = "FOO" }

  (* Bug 73: allow ulimit builtin *)
  test Shellvars.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 Shellvars.lns get "VAR1=\"this;is;a;test\"\nVAR2=this;\n" =
  { "VAR1" = "\"this;is;a;test\"" }
  { "VAR2" = "this" }

  (* Bug 230: parse conditions *)
  test Shellvars.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 Shellvars.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 Shellvars.lns get "VAR=value # eol-comment\n" =
  { "VAR" = "value"
    { "#comment" = "eol-comment" }
  }

  (* One-liners *)
  test Shellvars.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 Shellvars.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 Shellvars.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" = "f" } } }

  (* Select *)
  test Shellvars.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 Shellvars.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" = "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" = "${!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" }

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


Httpd: Define an eol_comment in section to allow for \n before comment

(* Apache HTTPD lens for Augeas

Authors:
  David Lutterkort <lutter@redhat.com>
  Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
  Raphael Pinson <raphink@gmail.com>

About: Reference
  Online Apache configuration manual: http://httpd.apache.org/docs/trunk/

About: License
    This file is licensed under the LGPL v2+.

About: Lens Usage
  Sample usage of this lens in augtool

  Apache configuration is represented by two main structures, nested sections
  and directives. Sections are used as labels, while directives are kept as a
  value. Sections and directives can have positional arguments inside values
  of "arg" nodes. Arguments of sections must be the firsts child of the
  section node.

  This lens doesn't support automatic string quoting. Hence, the string must
  be quoted when containing a space.

  Create a new VirtualHost section with one directive:
  > clear /files/etc/apache2/sites-available/foo/VirtualHost
  > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com"

About: Configuration files
  This lens applies to files in /etc/httpd and /etc/apache2. See <filter>.

*)

module Httpd =

autoload xfm

(******************************************************************
 *                           Utilities lens
 *****************************************************************)
let dels (s:string)     = del s s

(* deal with continuation lines *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "

let sep_osp             = Sep.opt_space
let sep_eq              = del /[ \t]*=[ \t]*/ "="

let nmtoken             = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
let word                = /[a-zA-Z][a-zA-Z0-9._-]*/

let comment             = Util.comment
let eol                 = Util.doseol
let empty               = Util.empty_dos
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^ '"\t\r\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let dquot = /"([^"\\\r\n]|\\\\.)*"/
let squot = /'([^'\\\r\n]|\\\\.)*'/

(******************************************************************
 *                            Attributes
 *****************************************************************)

let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
let arg_sec = [ label "arg" . store (char_arg_sec+|dquot|squot) ]

let argv (l:lens) = l . (sep_spc . l)*

let directive = [ indent . label "directive" . store word .
                  (sep_spc . argv arg_dir)? . eol ]

let section (body:lens) =
    let eol_comment = Util.comment_generic /[ \t\n]*#[ \t]*/ "# " in
    let inner = (sep_spc . argv arg_sec)? . sep_osp .
             dels ">" . (eol|eol_comment) . (body . (body|comment)*)? .
             indent . dels "</" in
    let kword = key word in
    let dword = del word "a" in
        [ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]

let rec content = section (content|directive|empty)

let lns = (content|directive|comment|empty)*

let filter = (incl "/etc/apache2/apache2.conf") .
             (incl "/etc/apache2/httpd.conf") .
             (incl "/etc/apache2/ports.conf") .
             (incl "/etc/apache2/conf.d/*") .
             (incl "/etc/apache2/mods-available/*") .
             (incl "/etc/apache2/sites-available/*") .
             (incl "/etc/httpd/conf.d/*.conf") .
             (incl "/etc/httpd/httpd.conf") .
             (incl "/etc/httpd/conf/httpd.conf") .
             Util.stdexcl

let xfm = transform lns filter

(* Apache HTTPD lens for Augeas

Authors:
  David Lutterkort <lutter@redhat.com>
  Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
  Raphael Pinson <raphink@gmail.com>

About: Reference
  Online Apache configuration manual: http://httpd.apache.org/docs/trunk/

About: License
    This file is licensed under the LGPL v2+.

About: Lens Usage
  Sample usage of this lens in augtool

  Apache configuration is represented by two main structures, nested sections
  and directives. Sections are used as labels, while directives are kept as a
  value. Sections and directives can have positional arguments inside values
  of "arg" nodes. Arguments of sections must be the firsts child of the
  section node.

  This lens doesn't support automatic string quoting. Hence, the string must
  be quoted when containing a space.

  Create a new VirtualHost section with one directive:
  > clear /files/etc/apache2/sites-available/foo/VirtualHost
  > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com"

About: Configuration files
  This lens applies to files in /etc/httpd and /etc/apache2. See <filter>.

*)

module Httpd =

autoload xfm

(******************************************************************
 *                           Utilities lens
 *****************************************************************)
let dels (s:string)     = del s s

(* deal with continuation lines *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "

let sep_osp             = Sep.opt_space
let sep_eq              = del /[ \t]*=[ \t]*/ "="

let nmtoken             = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
let word                = /[a-zA-Z][a-zA-Z0-9._-]*/

let comment             = Util.comment
let eol                 = Util.doseol
let empty               = Util.empty_dos
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^ '"\t\r\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let dquot = /"([^"\\\r\n]|\\\\.)*"/
let squot = /'([^'\\\r\n]|\\\\.)*'/

(******************************************************************
 *                            Attributes
 *****************************************************************)

let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
let arg_sec = [ label "arg" . store (char_arg_sec+|dquot|squot) ]

let argv (l:lens) = l . (sep_spc . l)*

let directive = [ indent . label "directive" . store word .
                  (sep_spc . argv arg_dir)? . eol ]

let section (body:lens) =
    let inner = (sep_spc . argv arg_sec)? . sep_osp .
             dels ">" . (eol|comment) . (body . (body|comment)*)? .
             indent . dels "</" in
    let kword = key word in
    let dword = del word "a" in
        [ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]

let rec content = section (content|directive|empty)

let lns = (content|directive|comment|empty)*

let filter = (incl "/etc/apache2/apache2.conf") .
             (incl "/etc/apache2/httpd.conf") .
             (incl "/etc/apache2/ports.conf") .
             (incl "/etc/apache2/conf.d/*") .
             (incl "/etc/apache2/mods-available/*") .
             (incl "/etc/apache2/sites-available/*") .
             (incl "/etc/httpd/conf.d/*.conf") .
             (incl "/etc/httpd/httpd.conf") .
             (incl "/etc/httpd/conf/httpd.conf") .
             Util.stdexcl

let xfm = transform lns filter


Add one-liner unit test for shellvars.aug

(* Test for shell lens *)
module Test_shellvars =

  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 Shellvars.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"   = "ONBOOT"
        { "#comment" = "We do not want this var" } }

  test Shellvars.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 Shellvars.lns get empty_val =
    { "EMPTY" = "" } { "DEVICE" = "eth0" }

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

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

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

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

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

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

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

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

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

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

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

  (* For some reason, `` conflicts with comment_eol *)
  test Shellvars.lns get "var=`ab#c`\n" =
    { "var" = "`ab"
       { "#comment" = "c`" } }

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

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

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

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

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

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

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

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

  test Shellvars.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 Shellvars.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 Shellvars.lns get "export FOO\n" =
  { "@export" = "FOO" }

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

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

  (* Bug 230: parse conditions *)
  test Shellvars.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 Shellvars.lns get "if [ -f /tmp/file1 ]; then
  if [ -f /tmp/file2 ]
  then
    . /tmp/file2; else; . /tmp/file4
  fi
else
  . /tmp/file3
fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { "@if" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
      { "@else"
        { ".source" = "/tmp/file4" }
      }
    }
    { "@else"
      { ".source" = "/tmp/file3" }
    }
  }

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

  (* One-liners *)
  test Shellvars.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" }
    }
  }

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

(* Test for shell lens *)
module Test_shellvars =

  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 Shellvars.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"   = "ONBOOT"
        { "#comment" = "We do not want this var" } }

  test Shellvars.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 Shellvars.lns get empty_val =
    { "EMPTY" = "" } { "DEVICE" = "eth0" }

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

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

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

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

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

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

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

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

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

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

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

  (* For some reason, `` conflicts with comment_eol *)
  test Shellvars.lns get "var=`ab#c`\n" =
    { "var" = "`ab"
       { "#comment" = "c`" } }

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

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

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

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

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

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

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

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

  test Shellvars.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 Shellvars.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 Shellvars.lns get "export FOO\n" =
  { "@export" = "FOO" }

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

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

  (* Bug 230: parse conditions *)
  test Shellvars.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 Shellvars.lns get "if [ -f /tmp/file1 ]; then
  if [ -f /tmp/file2 ]
  then
    . /tmp/file2; else; . /tmp/file4
  fi
else
  . /tmp/file3
fi\n" =
  { "@if" = "[ -f /tmp/file1 ]"
    { "@if" = "[ -f /tmp/file2 ]"
      { ".source" = "/tmp/file2" }
      { "@else"
        { ".source" = "/tmp/file4" }
      }
    }
    { "@else"
      { ".source" = "/tmp/file3" }
    }
  }

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

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


pg_hba: added debian systems path to filter - fixes #154.

(*
Module: Pg_Hba
  Parses PostgreSQL's pg_hba.conf

Author: Aurelien Bompard <aurelien@bompard.org>

About: Reference
  The file format is described in PostgreSQL's documentation:
  http://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html

About: License
  This file is licensed under the GPL.

About: Configuration files
  This lens applies to pg_hba.conf. See <filter> for exact locations.
*)

module Pg_Hba =
    autoload xfm

    (* Group: Generic primitives *)

    let eol     = Util.eol
    let word    = Rx.neg1
    (* Variable: ipaddr
       CIDR or ip+netmask *)
    let ipaddr   = /[0-9a-fA-F:\.]+(\/[0-9]+|[ \t]+[0-9\.]+)/

    let comma_sep_list (l:string) =
        let lns = [ label l . store word ] in
            Build.opt_list lns Sep.comma

    (* Group: Columns definitions *)

    (* View: database
       TODO: support for quoted strings *)
    let database = comma_sep_list "database"
    (* View: user
       TODO: support for quoted strings *)
    let user = comma_sep_list "user"
    (* View: address *)
    let address = [ label "address" . store ipaddr ]
    (* View: option
       part of <method> *)
    let option = [ label "option" . store word ]
    (* View: method
       can contain an <option> *)
    let method = [ label "method" . store Rx.word . ( Sep.tab . option )? ]

    (* Group: Records definitions *)

    (* View: record_local
       when type is "local", there is no "address" field *)
    let record_local = [ label "type" . store "local" ] . Sep.tab .
                       database . Sep.tab . user . Sep.tab . method

    (* Variable: remtypes
       non-local connection types *)
    let remtypes = "host" | "hostssl" | "hostnossl"
    (* View: record_remote *)
    let record_remote = [ label "type" . store remtypes ] . Sep.tab .
                        database . Sep.tab .  user . Sep.tab .
                        address . Sep.tab . method

    let record = [ seq "entries" . (record_local | record_remote) . eol ]

    let filter = (incl "/var/lib/pgsql/data/pg_hba.conf" .
                  incl "/etc/postgresql/*/*/pg_hba.conf" )

    let lns = ( record | Util.comment | Util.empty ) *

    let xfm = transform lns filter

(*
Module: Pg_Hba
  Parses /var/lib/pgsql/data/pg_hba.conf

Author: Aurelien Bompard <aurelien@bompard.org>

About: Reference
  The file format is described in PostgreSQL's documentation:
  http://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html

About: License
  This file is licensed under the GPL.

About: Configuration files
  This lens applies to /var/lib/pgsql/data/pg_hba.conf. See <filter>.
*)

module Pg_Hba =
    autoload xfm

    (* Group: Generic primitives *)

    let eol     = Util.eol
    let word    = Rx.neg1
    (* Variable: ipaddr
       CIDR or ip+netmask *)
    let ipaddr   = /[0-9a-fA-F:\.]+(\/[0-9]+|[ \t]+[0-9\.]+)/

    let comma_sep_list (l:string) =
        let lns = [ label l . store word ] in
            Build.opt_list lns Sep.comma

    (* Group: Columns definitions *)

    (* View: database
       TODO: support for quoted strings *)
    let database = comma_sep_list "database"
    (* View: user
       TODO: support for quoted strings *)
    let user = comma_sep_list "user"
    (* View: address *)
    let address = [ label "address" . store ipaddr ]
    (* View: option
       part of <method> *)
    let option = [ label "option" . store word ]
    (* View: method
       can contain an <option> *)
    let method = [ label "method" . store Rx.word . ( Sep.tab . option )? ]

    (* Group: Records definitions *)

    (* View: record_local
       when type is "local", there is no "address" field *)
    let record_local = [ label "type" . store "local" ] . Sep.tab .
                       database . Sep.tab . user . Sep.tab . method

    (* Variable: remtypes
       non-local connection types *)
    let remtypes = "host" | "hostssl" | "hostnossl"
    (* View: record_remote *)
    let record_remote = [ label "type" . store remtypes ] . Sep.tab .
                        database . Sep.tab .  user . Sep.tab .
                        address . Sep.tab . method

    let record = [ seq "entries" . (record_local | record_remote) . eol ]

    let filter = (incl "/var/lib/pgsql/data/pg_hba.conf")

    let lns = ( record | Util.comment | Util.empty ) *

    let xfm = transform lns filter


{ can be followed by spaces in xinetd.aug

(*
 * Module: Xinetd
 *   Parses xinetd configuration files
 *
 *  The structure of the lens and allowed attributes are ripped directly
 *  from xinetd's parser in xinetd/parse.c in xinetd's source checkout
 *  The downside of being so precise here is that if attributes are added
 *  they need to be added here, too. Writing a catchall entry, and getting
 *  to typecheck correctly would be a huge pain.
 *
 *  A really enterprising soul could tighten this down even further by
 *  restricting the acceptable values for each attribute.
 *
 * Author: David Lutterkort
 *)

module Xinetd =
  autoload xfm

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

  let name = key /[^ \t\n\/=+-]+/
  let bol_spc = del /[ \t]*/ "\t"
  let spc = del /[ \t]+/ " "
  let eol = del /[ \t]*\n/ "\n"

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

  let op = ([ label "add" . op_delim "+=" ]
           |[ label "del" . op_delim "-=" ]
           | eq)

  let value = store /[^ \t\n]+/

  let attr_one (n:regexp) =
    [ bol_spc . key n . eq . value . eol ]

  let attr_lst (n:regexp) (op_eq: lens) =
    [ bol_spc . key n . op_eq .
        [label "value" . value] . [label "value" . spc . value]* .
      eol ]

  let attr_lst_eq (n:regexp) = attr_lst n eq

  let attr_lst_op (n:regexp) = attr_lst n op

  (* Variable: service_attr
   *   Note:
   *      It is much faster to combine, for example, all the attr_one
   *      attributes into one regexp and pass that to a lens instead of
   *      using lens union (attr_one "a" | attr_one "b"|..) because the latter
   *      causes the type checker to work _very_ hard.
   *)

  let service_attr =
    attr_one ("socket_type" | "protocol" | "wait" | "user" | "group"
             |"server" | "instances"  | "rpc_version" | "rpc_number"
             | "id" | "port" | "nice" | "banner" | "bind" | "interface"
             | "per_source" | "groups" | "banner_success" | "banner_fail"
             | "disable" | "max_load" | "rlimit_as" | "rlimit_cpu"
             | "rlimit_data" | "rlimit_rss" | "rlimit_stack" | "v6only"
             | "deny_time" | "umask" | "mdns" | "libwrap")
   (* redirect and cps aren't really lists, they take exactly two values *)
   |attr_lst_eq ("server_args" | "log_type" |  "access_times" | "type"
                | "flags" | "redirect" | "cps")
   |attr_lst_op ( "log_on_success" | "log_on_failure"| "only_from"
                | "no_access" | "env" | "passenv")

  let default_attr =
    attr_one ( "instances" | "banner" | "bind" | "interface" | "per_source"
             | "groups" | "banner_success" | "banner_fail" | "max_load"
             | "v6only" | "umask" | "mdns")
   |attr_lst_eq "cps"       (* really only two values, not a whole list *)
   |attr_lst_op ( "log_type" | "log_on_success" | "log_on_failure" | "disabled"
                | "no_access" | "only_from" | "passenv" | "enabled" )

  (* View: body
   *   Note:
   *       We would really like to say "the body can contain any of a list
   *       of a list of attributes, each of them at most once"; but that
   *       would require that we build a lens that matches the permutation
   *       of all attributes; with around 40 individual attributes, that's
   *       not computationally feasible, even if we didn't have to worry
   *       about how to write that down. The resulting regular expressions
   *       would simply be prohibitively large.
   *)
  let body (attr:lens) = del /\n\{[ \t]*\n/ "\n{\n"
                       . (empty|comment|attr)*
                       . del /[ \t]*\}[ \t]*\n/ "}\n"

  (* View: includes
   *  Note:
   *   It would be nice if we could use the directories given in include and
   *   includedir directives to parse additional files instead of hardcoding
   *   all the places where xinetd config files can be found; but that is
   *   currently not possible, and implementing that has a good amount of
   *   hairy corner cases to consider.
   *)
  let includes = [ key /include|includedir/
                     . Util.del_ws_spc . store /[^ \t\n]+/ . eol ]

  let service =
     let sto_re = /[^# \t\n\/]+/ in
     [ key "service" . Sep.space . store sto_re . body service_attr ]

  let defaults = [ key "defaults" . del /[ \t]*/ "" . body default_attr ]

  let lns = ( empty | comment | includes | defaults | service )*

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

  let xfm = transform lns filter

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

(*
 * Module: Xinetd
 *   Parses xinetd configuration files
 *
 *  The structure of the lens and allowed attributes are ripped directly
 *  from xinetd's parser in xinetd/parse.c in xinetd's source checkout
 *  The downside of being so precise here is that if attributes are added
 *  they need to be added here, too. Writing a catchall entry, and getting
 *  to typecheck correctly would be a huge pain.
 *
 *  A really enterprising soul could tighten this down even further by
 *  restricting the acceptable values for each attribute.
 *
 * Author: David Lutterkort
 *)

module Xinetd =
  autoload xfm

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

  let name = key /[^ \t\n\/=+-]+/
  let bol_spc = del /[ \t]*/ "\t"
  let spc = del /[ \t]+/ " "
  let eol = del /[ \t]*\n/ "\n"

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

  let op = ([ label "add" . op_delim "+=" ]
           |[ label "del" . op_delim "-=" ]
           | eq)

  let value = store /[^ \t\n]+/

  let attr_one (n:regexp) =
    [ bol_spc . key n . eq . value . eol ]

  let attr_lst (n:regexp) (op_eq: lens) =
    [ bol_spc . key n . op_eq .
        [label "value" . value] . [label "value" . spc . value]* .
      eol ]

  let attr_lst_eq (n:regexp) = attr_lst n eq

  let attr_lst_op (n:regexp) = attr_lst n op

  (* Variable: service_attr
   *   Note:
   *      It is much faster to combine, for example, all the attr_one
   *      attributes into one regexp and pass that to a lens instead of
   *      using lens union (attr_one "a" | attr_one "b"|..) because the latter
   *      causes the type checker to work _very_ hard.
   *)

  let service_attr =
    attr_one ("socket_type" | "protocol" | "wait" | "user" | "group"
             |"server" | "instances"  | "rpc_version" | "rpc_number"
             | "id" | "port" | "nice" | "banner" | "bind" | "interface"
             | "per_source" | "groups" | "banner_success" | "banner_fail"
             | "disable" | "max_load" | "rlimit_as" | "rlimit_cpu"
             | "rlimit_data" | "rlimit_rss" | "rlimit_stack" | "v6only"
             | "deny_time" | "umask" | "mdns" | "libwrap")
   (* redirect and cps aren't really lists, they take exactly two values *)
   |attr_lst_eq ("server_args" | "log_type" |  "access_times" | "type"
                | "flags" | "redirect" | "cps")
   |attr_lst_op ( "log_on_success" | "log_on_failure"| "only_from"
                | "no_access" | "env" | "passenv")

  let default_attr =
    attr_one ( "instances" | "banner" | "bind" | "interface" | "per_source"
             | "groups" | "banner_success" | "banner_fail" | "max_load"
             | "v6only" | "umask" | "mdns")
   |attr_lst_eq "cps"       (* really only two values, not a whole list *)
   |attr_lst_op ( "log_type" | "log_on_success" | "log_on_failure" | "disabled"
                | "no_access" | "only_from" | "passenv" | "enabled" )

  (* View: body
   *   Note:
   *       We would really like to say "the body can contain any of a list
   *       of a list of attributes, each of them at most once"; but that
   *       would require that we build a lens that matches the permutation
   *       of all attributes; with around 40 individual attributes, that's
   *       not computationally feasible, even if we didn't have to worry
   *       about how to write that down. The resulting regular expressions
   *       would simply be prohibitively large.
   *)
  let body (attr:lens) = Util.del_str "\n{\n"
                       . (empty|comment|attr)*
                       . del /[ \t]*}[ \t]*\n/ "}\n"

  (* View: includes
   *  Note:
   *   It would be nice if we could use the directories given in include and
   *   includedir directives to parse additional files instead of hardcoding
   *   all the places where xinetd config files can be found; but that is
   *   currently not possible, and implementing that has a good amount of
   *   hairy corner cases to consider.
   *)
  let includes = [ key /include|includedir/
                     . Util.del_ws_spc . store /[^ \t\n]+/ . eol ]

  let service =
     let sto_re = /[^# \t\n\/]+/ in
     [ key "service" . Sep.space . store sto_re . body service_attr ]

  let defaults = [ key "defaults" . del /[ \t]*/ "" . body default_attr ]

  let lns = ( empty | comment | includes | defaults | service )*

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

  let xfm = transform lns filter

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


CronAllow: remove accompanying test, moved into Simplelines test earlier

module Test_cronallow =

  let example = "# Test comment
#
user1
another

user2
"

  test CronAllow.lns get example =
    { "#comment" = "Test comment" }
    { }
    { "1" = "user1" }
    { "2" = "another" }
    { }
    { "3" = "user2" }

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