dhcpd, removed unused variable and reformatted

stmt_subclass was a little too cluttered so I changed it to multiple
lines for readability.  I had also introduced an unused lens sep_to_ncl
which I removed.

(*
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\n]*\{([ \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\n]*=[ \t\n]*/ "="
let scl               = del ";" ";"

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

(* Define fields *)

(* adapted 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, apostrophe or slash
   *           which must be enclosed in double quotes
   *   squot - strings that contain an unescaped double quote
   *)
  let bare = del qchar? "" . store (bchar+) . del qchar? ""
  let quote = Quote.do_quote (store (bchar* . /[ \t'\/]/ . bchar*)+)
  let dquote = Quote.do_dquote (store (bchar+))
  (* these two are for special cases.  bare_to_scl is for any bareword that is
   * space or semicolon terminated.  dquote_any allows almost any character in
   * between the quotes. *)
  let bare_to_scl = Quote.do_dquote_opt(store /[^" \t\n;]+/)
  let dquote_any = Quote.do_dquote(store /[^"\n]*[ \t]+[^"\n]*/)

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"
                      | "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"
                      | "primary"
                      | "omapi-key"

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

let stmt_string  = stmt_string_tpl stmt_string_re bare
                 | stmt_string_tpl stmt_string_re quote
                 | stmt_string_tpl "filename" dquote

(************************************************************************
 *                         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|fddi/ ]
                        . sep_spc
                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         SET STATEMENTS
 *************************************************************************)
let stmt_set          = [ indent
                        . key "set"
                        . sep_spc
                        . store word
                        . sep_spc
                        . Sep.equal
                        . sep_spc
                        . [ label "value" . sto_to_scl ]
                        . sep_scl
                        . eos ]

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

let stmt_option_value = /((array of[ \t]+)?(((un)?signed[ \t]+)?integer (8|16|32)|string|ip6?-address|boolean|domain-list|text)|encapsulate [A-Za-z0-9_.-]+)/

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

let del_trail_spc = del /[ \t\n]*/ ""

let stmt_record = [ label "record" . counter "record" . Util.del_str "{"
                . sep_spc
                . ([seq "record" . store stmt_option_value . sep_com]*
                .  [seq "record" . store stmt_option_value . del_trail_spc])?
                . Util.del_str "}" ]

let stmt_types = store stmt_option_value|stmt_record

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

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" .  bare_to_scl ]|[ label "name" .  dquote_any ] )
                      . sep_spc
                      . ( [ label "value" . bare_to_scl ]|[ label "value" . dquote_any ] )
                      . 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/
                      | /known(-|[ ]+)clients/
                      | /all[ ]+clients/
                      | /dynamic[ ]+bootp[ ]+clients/
                      | /authenticated[ ]+clients/
                      | /unauthenticated[ ]+clients/
                      | "bootp"
                      | "booting"
                      | "duplicates"
                      | "declines"
                      | "client-updates"
                      | "leasequery"

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"

(* bare is anything but whitespace, quote marks or semicolon.
 * technically this should be locked down to mostly alphanumerics, but the
 * idea right now is just to make things work.  Also ideally I would use
 * dquote_space but I had a whale of a time with it.  It doesn't like
 * semicolon termination and my attempts to fix that led me to 3 hours of
 * frustration and back to this :)
 *)
let stmt_secu_tpl (l:lens) (s:string) =
                  [ indent . l . sep_spc . label s . bare_to_scl . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . dquote_any . 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_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
(* this is already the most complicated part of this module and it's about to
 * get worse.  match statements can be way more complicated than this
 *
 * examples:
 *      using or:
 *      match if ((option vendor-class-identifier="Banana Bready") or (option vendor-class-identifier="Cherry Sunfire"));
 *      unneeded parenthesis:
 *      match if (option vendor-class-identifier="Hello");
 *
 *      and of course the fact that the above two rules used one of infinately
 *      many potential options instead of a builtin function.
 *)
(* sto_com doesn't support quoted strings as arguments.  It also doesn't
   support single arguments (needs to match a comma) It will need to be
   updated for lcase, ucase and log to be workable.

   it also doesn't support no arguments, so gethostbyname() doesn't work.

   option and config-option are considered operators.  They should be matched
   in stmt_entry but also available under "match if" and "if" conditionals
   leased-address, host-decl-name, both take no args and return a value.  We
   might need to treat them as variable names in the parser.

   things like this may be near-impossible to parse even with recursion
   because we have no way of knowing when or if a subfunction takes arguments
   set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));

   even if we could parse it, they could get arbitrarily complicated like:
   binary-to-ascii(16, 8, ":", substring(hardware, 1, 6) and substring(hardware, 2, 3));

   so at some point we may need to programmatically knock it off and tell
   people to put weird stuff in an include file that augeas doesn't parse.

   the other option is to change the API to not parse the if statement at all,
   just pull in the conditional as a string.
 *)

let fct_re = "substring" | "binary-to-ascii" | "suffix" | "lcase" | "ucase"
             | "gethostbyname" | "packet"
             | "concat" | "reverse" | "encode-int"
             | "extract-int" | "lease-time" | "client-state" | "exists" | "known" | "static"
             | "pick-first-value" | "log" | "execute"

(* not needs to be different because it's a negation of whatever happens next *)
let op_re = "~="|"="|"~~"|"and"|"or"

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_ifopt = [ dels "if" . sep_spc . key "option" . sep_spc . store(word) .
                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) ]

let stmt_match_func = [ store fct_re . sep_osp . label "function" . fct_args ] .
                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ])

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 (dels "if" . sep_spc . stmt_match_func | stmt_match_pfv | stmt_match_ifopt)

(************************************************************************
 *                         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
                        | stmt_set
                        | 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"
                      | "group"
                      | "on"

let stmt_block_arg (body:lens)
                      = ([ indent . key stmt_block_arg_re . sep_spc . dquote_any . sep_obr . body* . sep_cbr ]
                         |[ indent . key stmt_block_arg_re . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr ]
                         |[ indent . key "key" . sep_spc . dquote_any . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ ""  ]
                         |[ indent . key "key" .  sep_spc . bare_to_scl . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ "" ])

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 conditional (body:lens) =
     let condition         = /[^{ \r\t\n][^{\n]*[^{ \r\t\n]|[^{ \t\n\r]/
  in let elsif = [ indent
                 . Build.xchgs "elsif" "@elsif"
                 . sep_spc
                 . store condition
                 . sep_obr
                 . body*
                 . sep_cbr ]
  in let else = [  indent
                 . Build.xchgs "else" "@else"
                 . sep_obr
                 . body*
                 . sep_cbr ]
  in [ indent
     . Build.xchgs "if" "@if"
     . sep_spc
     . store condition
     . sep_obr
     . body*
     . sep_cbr
     . elsif*
     . else? ]

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
    let lns4 = conditional body in
    (lns1 | lns2 | lns3 | lns4 | 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"

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_osp_or_nl     = del /[ \t\n]*/ ""
let sep_scl           = del /[ \t]*;([ \t]*\n)*/ ";\n"
let sep_obr           = del /[ \t\n]*\{([ \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\n]*=[ \t\n]*/ "="
let scl               = del ";" ";"

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

(* Define fields *)

(* adapted 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, apostrophe or slash
   *           which must be enclosed in double quotes
   *   squot - strings that contain an unescaped double quote
   *)
  let bare = del qchar? "" . store (bchar+) . del qchar? ""
  let quote = Quote.do_quote (store (bchar* . /[ \t'\/]/ . bchar*)+)
  let dquote = Quote.do_dquote (store (bchar+))
  (* these two are for special cases.  bare_to_scl is for any bareword that is
   * space or semicolon terminated.  dquote_any allows almost any character in
   * between the quotes. *)
  let bare_to_scl = Quote.do_dquote_opt(store /[^" \t\n;]+/)
  let dquote_any = Quote.do_dquote(store /[^"\n]*[ \t]+[^"\n]*/)

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"
                      | "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"
                      | "primary"
                      | "omapi-key"

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

let stmt_string  = stmt_string_tpl stmt_string_re bare
                 | stmt_string_tpl stmt_string_re quote
                 | stmt_string_tpl "filename" dquote

(************************************************************************
 *                         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|fddi/ ]
                        . sep_spc
                        . [ label "address" . store /[a-fA-F0-9:-]+/ ]
                        . sep_scl
                        . eos ]

(************************************************************************
 *                         SET STATEMENTS
 *************************************************************************)
let stmt_set          = [ indent
                        . key "set"
                        . sep_spc
                        . store word
                        . sep_spc
                        . Sep.equal
                        . sep_spc
                        . [ label "value" . sto_to_scl ]
                        . sep_scl
                        . eos ]

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

let stmt_option_value = /((array of[ \t]+)?(((un)?signed[ \t]+)?integer (8|16|32)|string|ip6?-address|boolean|domain-list|text)|encapsulate [A-Za-z0-9_.-]+)/

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

let del_trail_spc = del /[ \t\n]*/ ""

let stmt_record = [ label "record" . counter "record" . Util.del_str "{"
                . sep_spc
                . ([seq "record" . store stmt_option_value . sep_com]*
                .  [seq "record" . store stmt_option_value . del_trail_spc])?
                . Util.del_str "}" ]

let stmt_types = store stmt_option_value|stmt_record

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

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" .  bare_to_scl ]|[ label "name" .  dquote_any ])
                      . sep_spc . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) . 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/
                      | /known(-|[ ]+)clients/
                      | /all[ ]+clients/
                      | /dynamic[ ]+bootp[ ]+clients/
                      | /authenticated[ ]+clients/
                      | /unauthenticated[ ]+clients/
                      | "bootp"
                      | "booting"
                      | "duplicates"
                      | "declines"
                      | "client-updates"
                      | "leasequery"

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"

(* bare is anything but whitespace, quote marks or semicolon.
 * technically this should be locked down to mostly alphanumerics, but the
 * idea right now is just to make things work.  Also ideally I would use
 * dquote_space but I had a whale of a time with it.  It doesn't like
 * semicolon termination and my attempts to fix that led me to 3 hours of
 * frustration and back to this :)
 *)
let stmt_secu_tpl (l:lens) (s:string) =
                  [ indent . l . sep_spc . label s . bare_to_scl . sep_scl . eos ] |
                  [ indent . l . sep_spc . label s . dquote_any . 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_com = /[^ \t\n,\(\)][^,\(\)]*[^ \t\n,\(\)]|[^ \t\n,\(\)]+/ | word . /[ \t]*\([^)]*\)/
(* this is already the most complicated part of this module and it's about to
 * get worse.  match statements can be way more complicated than this
 *
 * examples:
 *      using or:
 *      match if ((option vendor-class-identifier="Banana Bready") or (option vendor-class-identifier="Cherry Sunfire"));
 *      unneeded parenthesis:
 *      match if (option vendor-class-identifier="Hello");
 *
 *      and of course the fact that the above two rules used one of infinately
 *      many potential options instead of a builtin function.
 *)
(* sto_com doesn't support quoted strings as arguments.  It also doesn't
   support single arguments (needs to match a comma) It will need to be
   updated for lcase, ucase and log to be workable.

   it also doesn't support no arguments, so gethostbyname() doesn't work.

   option and config-option are considered operators.  They should be matched
   in stmt_entry but also available under "match if" and "if" conditionals
   leased-address, host-decl-name, both take no args and return a value.  We
   might need to treat them as variable names in the parser.

   things like this may be near-impossible to parse even with recursion
   because we have no way of knowing when or if a subfunction takes arguments
   set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));

   even if we could parse it, they could get arbitrarily complicated like:
   binary-to-ascii(16, 8, ":", substring(hardware, 1, 6) and substring(hardware, 2, 3));

   so at some point we may need to programmatically knock it off and tell
   people to put weird stuff in an include file that augeas doesn't parse.

   the other option is to change the API to not parse the if statement at all,
   just pull in the conditional as a string.
 *)

let fct_re = "substring" | "binary-to-ascii" | "suffix" | "lcase" | "ucase"
             | "gethostbyname" | "packet"
             | "concat" | "reverse" | "encode-int"
             | "extract-int" | "lease-time" | "client-state" | "exists" | "known" | "static"
             | "pick-first-value" | "log" | "execute"

(* not needs to be different because it's a negation of whatever happens next *)
let op_re = "~="|"="|"~~"|"and"|"or"

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_ifopt = [ dels "if" . sep_spc . key "option" . sep_spc . store(word) .
                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ]) ]

let stmt_match_func = [ store fct_re . sep_osp . label "function" . fct_args ] .
                      sep_eq . ([ label "value" . bare_to_scl ]|[ label "value" . dquote_any ])

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 (dels "if" . sep_spc . stmt_match_func | stmt_match_pfv | stmt_match_ifopt)

(************************************************************************
 *                         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
                        | stmt_set
                        | 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"
                      | "group"
                      | "on"

let stmt_block_arg (body:lens)
                      = ([ indent . key stmt_block_arg_re . sep_spc . dquote_any . sep_obr . body* . sep_cbr ]
                         |[ indent . key stmt_block_arg_re . sep_spc . bare_to_scl . sep_obr . body* . sep_cbr ]
                         |[ indent . key "key" . sep_spc . dquote_any . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ ""  ]
                         |[ indent . key "key" .  sep_spc . bare_to_scl . sep_obr . body* . sep_cbr . del /(;([ \t]*\n)*)?/ "" ])

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 conditional (body:lens) =
     let condition         = /[^{ \r\t\n][^{\n]*[^{ \r\t\n]|[^{ \t\n\r]/
  in let elsif = [ indent
                 . Build.xchgs "elsif" "@elsif"
                 . sep_spc
                 . store condition
                 . sep_obr
                 . body*
                 . sep_cbr ]
  in let else = [  indent
                 . Build.xchgs "else" "@else"
                 . sep_obr
                 . body*
                 . sep_cbr ]
  in [ indent
     . Build.xchgs "if" "@if"
     . sep_spc
     . store condition
     . sep_obr
     . body*
     . sep_cbr
     . elsif*
     . else? ]

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
    let lns4 = conditional body in
    (lns1 | lns2 | lns3 | lns4 | 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"

let xfm = transform lns filter


Fix author

Fix author

(*
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 LGPLv2+, 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

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

Author: Raphael Pinson <raphink@gmail.com>

About: Reference

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/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


* tests/modules/pass_strip_quotes.aug: test quote stripping

The tests strips optional quotes and puts them back (or creates them) as
needed, only depending on the value of the tree node.

(* Demonstrate how quotes can be stripped from values.            *)
(* Values can be enclosed in single or double quotes, if they     *)
(* contain spaces they _have_ to be enclosed in quotes. Since     *)
(* everything's regular, we can't actually match a closing quote  *)
(* to the opening quote, so "hello' will be accepted, too.        *)

module Pass_strip_quotes =

  let nuttin = del /(""|'')?/ "''"
  let bare = del /["']?/ "" . store /[a-zA-Z0-9]+/ . del /["']?/ ""
  let quoted = del /["']/ "'" . store /.*[ \t].*/ . del /["']/ "'"

  let lns = [ label "foo" . bare ]
    | [ label "foo" . quoted ]
    | [ label "foo" . nuttin ]

  test lns get "'hello'" = { "foo" = "hello" }
  test lns get "'hello world'" = { "foo" = "hello world" }

  let hw = "'hello world'"
  let set_hw = set "/foo" "hello world"
  test lns put "hello" after set_hw = hw
  test lns put "'hello world'" after set "/foo" "hello" = "'hello'"

  test lns put "" after set_hw = hw
  test lns put "\"\"" after set_hw = hw

error: pathspec 'tests/modules/pass_strip_quotes.aug' did not match any file(s) known to git


Add basic EPSG augeas lens

Add basic EPSG augeas lens

module EPSG =

let proj = [ key /[^\/= \t\n]+/ . (Sep.equal . store Rx.no_spaces)? ]

let entry = [ Util.del_str "<" . key Rx.integer . Util.del_str ">"
            . Sep.space . Build.opt_list proj Sep.space . Sep.space
            . Util.del_str "<>" . Util.eol ]

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

error: pathspec 'files/lenses/epsg.aug' did not match any file(s) known to git


Augeas file for log4j.properties

Augeas file for log4j.properties

transform Properties.lns incl /atlassian-jira/WEB-INF/classes/log4j.properties
load
set /augeas/context /files/atlassian-jira/WEB-INF/classes/log4j.properties
set log4j.logger.org.apache.catalina.webresources.Cache "ERROR, console, filelog"
save

error: pathspec 'adjust-config-files.aug' did not match any file(s) known to git


delete unused augeas file

delete unused augeas file

(* PHP module for Augeas                      *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(*                                            *)

module PHP =
  autoload xfm

(************************************************************************
 * INI File settings
 *************************************************************************)

let comment  = IniFile.comment IniFile.comment_re IniFile.comment_default
let sep      = IniFile.sep IniFile.sep_re IniFile.sep_default
let empty    = IniFile.empty

(************************************************************************
 *                        ENTRY
 *
 * We have to remove the keyword "section" from possible entry keywords
 * otherwise it would lead to an ambiguity with the "section" label
 * since PHP allows entries outside of sections.
 *************************************************************************)
let entry    =
     let word = IniFile.entry_re
  in let entry_re = word . ( "[" . word . "]" )?
  in IniFile.indented_entry entry_re sep comment

(************************************************************************
 *                         TITLE
 *
 * We use IniFile.title_label because there can be entries
 * outside of sections whose labels would conflict with section names
 *************************************************************************)
let title       = IniFile.title ( IniFile.record_re - ".anon" )
let record      = IniFile.record title entry

let record_anon = [ label ".anon" . ( entry | empty )+ ]

(************************************************************************
 *                         LENS & FILTER
 * There can be entries before any section
 * IniFile.entry includes comment management, so we just pass entry to lns
 *************************************************************************)
let lns    = record_anon? . record*

let filter = (incl "/etc/php*/*/*.ini")
             . (incl "/etc/php.ini")
             . (incl "/etc/php.d/*.ini")
             (* PHPFPM Support *)
             . (incl "/etc/php*/fpm/pool.d/*.conf")
             . (incl "/etc/php-fpm.d/*.conf")
             (* Zend Community edition *)
             . (incl "/usr/local/zend/etc/php.ini")
             . (incl "/usr/local/zend/etc/conf.d/*.ini")
             . Util.stdexcl

let xfm = transform lns filter


Dput: add 'allow_dcut' parameter

Dput: add 'allow_dcut' parameter

Fixes bug #105

(* Dput module for Augeas
   Author: Raphael Pinson <raphink@gmail.com>

   Reference: dput uses Python's ConfigParser:
     http://docs.python.org/lib/module-ConfigParser.html
*)

module Dput =
  autoload xfm

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

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

let setting = "allow_dcut"
            | "allow_non-us_software"
            | "allow_unsigned_uploads"
            | "check_version"
            | "default_host_main"
            | "default_host_non-us"
            | "fqdn"
            | "hash"
            | "incoming"
            | "login"
            | "method"
            | "passive_ftp"
            | "post_upload_command"
            | "pre_upload_command"
            | "progress_indicator"
            | "run_dinstall"
            | "run_lintian"
            | "scp_compress"
            | "ssh_config_options"
            | "allowed_distributions"

(************************************************************************
 * "name: value" entries, with continuations in the style of RFC 822;
 * "name=value" is also accepted
 * leading whitespace is removed from values
 *************************************************************************)
let entry = IniFile.entry setting sep comment

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

let lns    = IniFile.lns record comment

let filter = (incl "/etc/dput.cf")
           . (incl "~/.dput.cf")
           . Util.stdexcl

let xfm = transform lns filter

(* Dput module for Augeas
   Author: Raphael Pinson <raphink@gmail.com>

   Reference: dput uses Python's ConfigParser:
     http://docs.python.org/lib/module-ConfigParser.html
*)

module Dput =
  autoload xfm

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

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

let setting = "allow_non-us_software"
            | "allow_unsigned_uploads"
            | "check_version"
            | "default_host_main"
            | "default_host_non-us"
            | "fqdn"
            | "hash"
            | "incoming"
            | "login"
            | "method"
            | "passive_ftp"
            | "post_upload_command"
            | "pre_upload_command"
            | "progress_indicator"
            | "run_dinstall"
            | "run_lintian"
            | "scp_compress"
            | "ssh_config_options"
            | "allowed_distributions"

(************************************************************************
 * "name: value" entries, with continuations in the style of RFC 822;
 * "name=value" is also accepted
 * leading whitespace is removed from values
 *************************************************************************)
let entry = IniFile.entry setting sep comment

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

let lns    = IniFile.lns record comment

let filter = (incl "/etc/dput.cf")
           . (incl "~/.dput.cf")
           . Util.stdexcl

let xfm = transform lns filter


Grub: add /boot/grub/grub.conf to transform

(*
Module: Grub
  Parses grub configuration

Author: David Lutterkort <lutter@redhat.com>

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

About: Lens Usage
   To be documented
*)

module Grub =
  autoload xfm

    (* This only covers the most basic grub directives. Needs to be *)
    (* expanded to cover more (and more esoteric) directives        *)
    (* It is good enough to handle the grub.conf on my Fedora 8 box *)

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

    (* View: value_to_eol *)
    let value_to_eol = store /[^= \t\n][^\n]*[^= \t\n]|[^= \t\n]/

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

    (* View: del_to_eol *)
    let del_to_eol = del /[^ \t\n]*/ ""

    (* View: spc *)
    let spc = Util.del_ws_spc

    (* View: opt_ws *)
    let opt_ws = Util.del_opt_ws ""

    (* View: dels *)
    let dels (s:string) = Util.del_str s

    (* View: eq *)
    let eq = dels "="

    (* View: switch *)
    let switch (n:regexp) = dels "--" . key n

    (* View: switch_arg *)
    let switch_arg (n:regexp) = switch n . eq . store Rx.no_spaces

    (* View: value_sep *)
    let value_sep (dflt:string) = del /[ \t]*[ \t=][ \t]*/ dflt

    (* View: comment_re *)
    let comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                       - /# ## (Start|End) Default Options ##/

    (* View: comment *)
    let comment    =
        [ Util.indent . label "#comment" . del /#[ \t]*/ "# "
            . store comment_re . eol ]

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

(************************************************************************
 * Group:                 USEFUL FUNCTIONS
 *************************************************************************)

    (* View: command *)
    let command (kw:regexp) (indent:string) =
      Util.del_opt_ws indent . key kw

    (* View: kw_arg *)
    let kw_arg (kw:regexp) (indent:string) (dflt_sep:string) =
      [ command kw indent . value_sep dflt_sep . value_to_eol . eol ]

    (* View: kw_boot_arg *)
    let kw_boot_arg (kw:regexp) = kw_arg kw "\t" " "

    (* View: kw_menu_arg *)
    let kw_menu_arg (kw:regexp) = kw_arg kw "" " "

    (* View: password_arg *)
    let password_arg = [ key "password" .
      (spc . [ switch "md5" ])? .
      (spc . [ switch "encrypted" ])? .
      spc . store (/[^ \t\n]+/ - /--[^ \t\n]+/) .
      (spc . [ label "file" . store /[^ \t\n]+/ ])? .
      eol ]

    (* View: kw_pres *)
    let kw_pres (kw:string) = [ opt_ws . key kw . del_to_eol . eol ]

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

    (* View: device
     *  This is a shell-only directive in upstream grub; the grub versions
     *  in at least Fedora/RHEL use this to find devices for UEFI boot *)
    let device =
	  [ command "device" "" . Sep.space . store /\([A-Za-z0-9_.-]+\)/ . spc .
		  [ label "file" . value_to_eol ] . Util.eol ]

    (* View: color *)
    let color =
      (* Should we nail it down to exactly the color names that *)
      (* grub supports ? *)
      let color_name = store /[A-Za-z-]+/ in
      let color_spec =
        [ label "foreground" . color_name] .
        dels "/" .
        [ label "background" . color_name ] in
      [ opt_ws . key "color" .
        spc . [ label "normal" . color_spec ] .
        (spc . [ label "highlight" . color_spec ])? .
        eol ]

    (* View: serial *)
    let serial =
      [ command "serial" "" .
        [ spc . switch_arg /unit|port|speed|word|parity|stop|device/ ]* .
        eol ]

    (* View: terminal *)
    let terminal =
      [ command "terminal" "" .
          ([ spc . switch /dumb|no-echo|no-edit|silent/ ]
          |[ spc . switch_arg /timeout|lines/ ])* .
          [ spc . key /console|serial|hercules/ ]* . eol ]

    (* View: menu_setting *)
    let menu_setting = kw_menu_arg "default"
                     | kw_menu_arg "fallback"
                     | kw_pres "hiddenmenu"
                     | kw_menu_arg "timeout"
                     | kw_menu_arg "splashimage"
                     | kw_menu_arg "gfxmenu"
                     | kw_menu_arg "background"
                     | serial
                     | terminal
                     | password_arg
                     | color
		     | device

    (* View: title *)
    let title = del /title[ \t=]+/ "title " . value_to_eol . eol

    (* View: multiboot_arg
     *  Permits a second form for Solaris multiboot kernels that
     *  take a path (with a slash) as their first arg, e.g.
     *  /boot/multiboot kernel/unix another=arg *)
    let multiboot_arg = [ label "@path" .
                          store (Rx.word . "/" . Rx.no_spaces) ]

    (* View: kernel_args
        Parse the file name and args on a kernel or module line. *)
    let kernel_args =
      let arg = /[A-Za-z0-9_.$-]+/ - /type|no-mem-option/  in
      store /(\([a-z0-9,]+\))?\/[^ \t\n]*/ .
            (spc . multiboot_arg)? .
            (spc . [ key arg . (eq. store /([^ \t\n])*/)?])* . eol

    (* View: module_line
        Solaris extension adds module$ and kernel$ for variable interpolation *)
    let module_line =
      [ command /module\$?/ "\t" . spc . kernel_args ]

    (* View: map_line *)
    let map_line =
      [ command "map" "\t" . spc .
           [ label "from" . store /[()A-za-z0-9]+/ ] . spc .
           [ label "to" . store /[()A-za-z0-9]+/ ] . eol ]

    (* View: kernel *)
    let kernel =
        [ command /kernel\$?/ "\t" .
          (spc .
             ([switch "type" . eq . store /[a-z]+/]
             |[switch "no-mem-option"]))* .
          spc . kernel_args ]

    (* View: chainloader *)
    let chainloader =
      [ command "chainloader" "\t" .
          [ spc . switch "force" ]? . spc . store Rx.no_spaces . eol ]

    (* View: savedefault *)
    let savedefault =
      [ command "savedefault" "\t" . (spc . store Rx.integer)? . eol ]

    (* View: configfile *)
    let configfile =
      [ command "configfile" "\t" . spc . store Rx.no_spaces . eol ]

    (* View: boot_setting
        <boot> entries *)
    let boot_setting =
          let boot_arg_re = "root" | "initrd" | "rootnoverify" | "uuid"
                          | "findroot" | "bootfs" (* Solaris extensions *)
       in kw_boot_arg boot_arg_re
        | kernel
        | chainloader
        | kw_pres "quiet"  (* Seems to be a Ubuntu extension *)
        | savedefault
        | configfile
        | module_line
        | map_line

    (* View: boot *)
    let boot =
      let line = ((boot_setting|comment)* . boot_setting)? in
      [ label "title" . title . line ]

(************************************************************************
 * Group:                 DEBIAN-SPECIFIC SECTIONS
 *************************************************************************)

    (* View: debian_header
        Header for a <debian>-specific section *)
    let debian_header  = "## ## Start Default Options ##\n"

    (* View: debian_footer
        Footer for a <debian>-specific section *)
    let debian_footer  = "## ## End Default Options ##\n"

    (* View: debian_comment_re *)
    let debian_comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                            - "## End Default Options ##"

    (* View: debian_comment
        A comment entry inside a <debian>-specific section *)
    let debian_comment =
        [ Util.indent . label "#comment" . del /##[ \t]*/ "## "
            . store debian_comment_re . eol ]

    (* View: debian_setting_re *)
    let debian_setting_re = "kopt"
                          | "groot"
                          | "alternative"
                          | "lockalternative"
                          | "defoptions"
                          | "lockold"
                          | "xenhopt"
                          | "xenkopt"
                          | "altoptions"
                          | "howmany"
                          | "memtest86"
                          | "updatedefaultentry"
                          | "savedefault"
                          | "indomU"

    (* View: debian_entry *)
    let debian_entry   = [ Util.del_str "#" . Util.indent
                         . key debian_setting_re . del /[ \t]*=/ "="
                         . value_to_eol? . eol ]

    (* View: debian
        A debian-specific section, made of <debian_entry> lines *)
    let debian         = [ label "debian"
                    . del debian_header debian_header
                    . (debian_comment|empty|debian_entry)*
                    . del debian_footer debian_footer ]

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

    (* View: lns *)
    let lns = (comment | empty | menu_setting | boot | debian)*

    (* View: filter *)
    let filter = incl "/boot/grub/grub.conf"
               . incl "/boot/grub/menu.lst"
               . incl "/etc/grub.conf"

    let xfm = transform lns filter

(*
Module: Grub
  Parses grub configuration

Author: David Lutterkort <lutter@redhat.com>

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

About: Lens Usage
   To be documented
*)

module Grub =
  autoload xfm

    (* This only covers the most basic grub directives. Needs to be *)
    (* expanded to cover more (and more esoteric) directives        *)
    (* It is good enough to handle the grub.conf on my Fedora 8 box *)

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

    (* View: value_to_eol *)
    let value_to_eol = store /[^= \t\n][^\n]*[^= \t\n]|[^= \t\n]/

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

    (* View: del_to_eol *)
    let del_to_eol = del /[^ \t\n]*/ ""

    (* View: spc *)
    let spc = Util.del_ws_spc

    (* View: opt_ws *)
    let opt_ws = Util.del_opt_ws ""

    (* View: dels *)
    let dels (s:string) = Util.del_str s

    (* View: eq *)
    let eq = dels "="

    (* View: switch *)
    let switch (n:regexp) = dels "--" . key n

    (* View: switch_arg *)
    let switch_arg (n:regexp) = switch n . eq . store Rx.no_spaces

    (* View: value_sep *)
    let value_sep (dflt:string) = del /[ \t]*[ \t=][ \t]*/ dflt

    (* View: comment_re *)
    let comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                       - /# ## (Start|End) Default Options ##/

    (* View: comment *)
    let comment    =
        [ Util.indent . label "#comment" . del /#[ \t]*/ "# "
            . store comment_re . eol ]

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

(************************************************************************
 * Group:                 USEFUL FUNCTIONS
 *************************************************************************)

    (* View: command *)
    let command (kw:regexp) (indent:string) =
      Util.del_opt_ws indent . key kw

    (* View: kw_arg *)
    let kw_arg (kw:regexp) (indent:string) (dflt_sep:string) =
      [ command kw indent . value_sep dflt_sep . value_to_eol . eol ]

    (* View: kw_boot_arg *)
    let kw_boot_arg (kw:regexp) = kw_arg kw "\t" " "

    (* View: kw_menu_arg *)
    let kw_menu_arg (kw:regexp) = kw_arg kw "" " "

    (* View: password_arg *)
    let password_arg = [ key "password" .
      (spc . [ switch "md5" ])? .
      (spc . [ switch "encrypted" ])? .
      spc . store (/[^ \t\n]+/ - /--[^ \t\n]+/) .
      (spc . [ label "file" . store /[^ \t\n]+/ ])? .
      eol ]

    (* View: kw_pres *)
    let kw_pres (kw:string) = [ opt_ws . key kw . del_to_eol . eol ]

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

    (* View: device
     *  This is a shell-only directive in upstream grub; the grub versions
     *  in at least Fedora/RHEL use this to find devices for UEFI boot *)
    let device =
	  [ command "device" "" . Sep.space . store /\([A-Za-z0-9_.-]+\)/ . spc .
		  [ label "file" . value_to_eol ] . Util.eol ]

    (* View: color *)
    let color =
      (* Should we nail it down to exactly the color names that *)
      (* grub supports ? *)
      let color_name = store /[A-Za-z-]+/ in
      let color_spec =
        [ label "foreground" . color_name] .
        dels "/" .
        [ label "background" . color_name ] in
      [ opt_ws . key "color" .
        spc . [ label "normal" . color_spec ] .
        (spc . [ label "highlight" . color_spec ])? .
        eol ]

    (* View: serial *)
    let serial =
      [ command "serial" "" .
        [ spc . switch_arg /unit|port|speed|word|parity|stop|device/ ]* .
        eol ]

    (* View: terminal *)
    let terminal =
      [ command "terminal" "" .
          ([ spc . switch /dumb|no-echo|no-edit|silent/ ]
          |[ spc . switch_arg /timeout|lines/ ])* .
          [ spc . key /console|serial|hercules/ ]* . eol ]

    (* View: menu_setting *)
    let menu_setting = kw_menu_arg "default"
                     | kw_menu_arg "fallback"
                     | kw_pres "hiddenmenu"
                     | kw_menu_arg "timeout"
                     | kw_menu_arg "splashimage"
                     | kw_menu_arg "gfxmenu"
                     | kw_menu_arg "background"
                     | serial
                     | terminal
                     | password_arg
                     | color
		     | device

    (* View: title *)
    let title = del /title[ \t=]+/ "title " . value_to_eol . eol

    (* View: multiboot_arg
     *  Permits a second form for Solaris multiboot kernels that
     *  take a path (with a slash) as their first arg, e.g.
     *  /boot/multiboot kernel/unix another=arg *)
    let multiboot_arg = [ label "@path" .
                          store (Rx.word . "/" . Rx.no_spaces) ]

    (* View: kernel_args
        Parse the file name and args on a kernel or module line. *)
    let kernel_args =
      let arg = /[A-Za-z0-9_.$-]+/ - /type|no-mem-option/  in
      store /(\([a-z0-9,]+\))?\/[^ \t\n]*/ .
            (spc . multiboot_arg)? .
            (spc . [ key arg . (eq. store /([^ \t\n])*/)?])* . eol

    (* View: module_line
        Solaris extension adds module$ and kernel$ for variable interpolation *)
    let module_line =
      [ command /module\$?/ "\t" . spc . kernel_args ]

    (* View: map_line *)
    let map_line =
      [ command "map" "\t" . spc .
           [ label "from" . store /[()A-za-z0-9]+/ ] . spc .
           [ label "to" . store /[()A-za-z0-9]+/ ] . eol ]

    (* View: kernel *)
    let kernel =
        [ command /kernel\$?/ "\t" .
          (spc .
             ([switch "type" . eq . store /[a-z]+/]
             |[switch "no-mem-option"]))* .
          spc . kernel_args ]

    (* View: chainloader *)
    let chainloader =
      [ command "chainloader" "\t" .
          [ spc . switch "force" ]? . spc . store Rx.no_spaces . eol ]

    (* View: savedefault *)
    let savedefault =
      [ command "savedefault" "\t" . (spc . store Rx.integer)? . eol ]

    (* View: configfile *)
    let configfile =
      [ command "configfile" "\t" . spc . store Rx.no_spaces . eol ]

    (* View: boot_setting
        <boot> entries *)
    let boot_setting =
          let boot_arg_re = "root" | "initrd" | "rootnoverify" | "uuid"
                          | "findroot" | "bootfs" (* Solaris extensions *)
       in kw_boot_arg boot_arg_re
        | kernel
        | chainloader
        | kw_pres "quiet"  (* Seems to be a Ubuntu extension *)
        | savedefault
        | configfile
        | module_line
        | map_line

    (* View: boot *)
    let boot =
      let line = ((boot_setting|comment)* . boot_setting)? in
      [ label "title" . title . line ]

(************************************************************************
 * Group:                 DEBIAN-SPECIFIC SECTIONS
 *************************************************************************)

    (* View: debian_header
        Header for a <debian>-specific section *)
    let debian_header  = "## ## Start Default Options ##\n"

    (* View: debian_footer
        Footer for a <debian>-specific section *)
    let debian_footer  = "## ## End Default Options ##\n"

    (* View: debian_comment_re *)
    let debian_comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                            - "## End Default Options ##"

    (* View: debian_comment
        A comment entry inside a <debian>-specific section *)
    let debian_comment =
        [ Util.indent . label "#comment" . del /##[ \t]*/ "## "
            . store debian_comment_re . eol ]

    (* View: debian_setting_re *)
    let debian_setting_re = "kopt"
                          | "groot"
                          | "alternative"
                          | "lockalternative"
                          | "defoptions"
                          | "lockold"
                          | "xenhopt"
                          | "xenkopt"
                          | "altoptions"
                          | "howmany"
                          | "memtest86"
                          | "updatedefaultentry"
                          | "savedefault"
                          | "indomU"

    (* View: debian_entry *)
    let debian_entry   = [ Util.del_str "#" . Util.indent
                         . key debian_setting_re . del /[ \t]*=/ "="
                         . value_to_eol? . eol ]

    (* View: debian
        A debian-specific section, made of <debian_entry> lines *)
    let debian         = [ label "debian"
                    . del debian_header debian_header
                    . (debian_comment|empty|debian_entry)*
                    . del debian_footer debian_footer ]

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

    (* View: lns *)
    let lns = (comment | empty | menu_setting | boot | debian)*

    (* View: filter *)
    let filter = incl "/boot/grub/menu.lst"
               . incl "/etc/grub.conf"

    let xfm = transform lns filter


Add /etc/mtab to known files

Add /etc/mtab to known files

(* Parsing /etc/fstab *)

module Fstab =
  autoload xfm

  let sep_tab = Sep.tab
  let sep_spc = Sep.space
  let comma   = Sep.comma
  let eol     = Util.eol

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

  let word    = Rx.neg1
  let spec    = /[^,# \n\t][^ \n\t]*/

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

  let record = [ seq "mntent" .
                   [ label "spec" . store spec ] . sep_tab .
                   [ label "file" . store word ] . sep_tab .
                   comma_sep_list "vfstype" . sep_tab .
                   comma_sep_list "opt" .
                   (sep_tab . [ label "dump" . store /[0-9]+/ ] .
                    ( sep_spc . [ label "passno" . store /[0-9]+/ ])? )?
                 . eol ]

  let lns = ( empty | comment | record ) *
  let filter = (incl "/etc/fstab")
             . (incl "/etc/mtab")

  let xfm = transform lns filter

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

(* Parsing /etc/fstab *)

module Fstab =
  autoload xfm

  let sep_tab = Sep.tab
  let sep_spc = Sep.space
  let comma   = Sep.comma
  let eol     = Util.eol

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

  let word    = Rx.neg1
  let spec    = /[^,# \n\t][^ \n\t]*/

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

  let record = [ seq "mntent" .
                   [ label "spec" . store spec ] . sep_tab .
                   [ label "file" . store word ] . sep_tab .
                   comma_sep_list "vfstype" . sep_tab .
                   comma_sep_list "opt" .
                   (sep_tab . [ label "dump" . store /[0-9]+/ ] .
                    ( sep_spc . [ label "passno" . store /[0-9]+/ ])? )?
                 . eol ]

  let lns = ( empty | comment | record ) *

  let xfm = transform lns (incl "/etc/fstab")

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


Yum.lns: simplify code for combinatory logic of list_entry elements


Add lens for radicale config file

Add lens for radicale config file

error: pathspec 'lenses/radicale.aug' did not match any file(s) known to git


Inifile: no space around seps by default

(* IniFile generic module for Augeas          *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(*                                            *)
(* TODO: Support double quotes in value       *)

module IniFile  =

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

let eol                  = Util.eol

let sep_gen (pat:regexp) (default:string)
                       = Util.del_opt_ws "" . del pat default
let value_sep          = sep_gen "=" "="
let value_sepwithcolon = sep_gen /[:=]/ "="

let value_to_eol       = Util.del_opt_ws ""
                         . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/
let value_to_comment   = Util.del_opt_ws ""
                         . store /[^;# \t\n][^;#\n]*[^;# \t\n]|[^;# \t\n]/

(* Define comment and empty strings *)
(* Some implementations of INI file allow "#" as a comment sign *)

let comment_generic (pat:regexp) (default:string)
                         = [ label "comment" . sep_gen pat default
			   .  value_to_eol . eol ]
let comment              = comment_generic /[#;]/ ";"
let comment_nosharp      = comment_generic ";" ";"

let empty                = Util.empty

(************************************************************************
 *                             ENTRY
 *************************************************************************)

(* Some implementations of INI file allow ":" as separator *)
let entry_generic (kw:regexp) (sep:lens) (comment:lens)
                    = [ key kw . sep . value_to_comment? . (comment|eol) ]
let entry (kw:regexp)
                    = entry_generic kw value_sepwithcolon comment
let entry_setcomment (kw:regexp) (comment:lens)
                    = entry_generic kw value_sepwithcolon comment
let entry_nocolon (kw:regexp)
                    = entry_generic kw value_sep comment
let entry_nocolon_setcomment (kw:regexp) (comment:lens)
                    = entry_generic kw value_sep comment

(************************************************************************
 *                             RECORD
 *************************************************************************)

let title                = Util.del_str "[" . store /[^]]+/
                           . Util.del_str "]". eol

let record (label_name:string) (entry:lens)
                         = [ label label_name  . title
			   . (entry | comment | empty)* ]

let record_setcomment (label_name:string) (entry:lens)
          (comment:lens) = [ label label_name . title
                           . (entry | comment | empty)* ]

(* Some implementations of INI File do not allow empty lines *)
let record_noempty (label_name:string) (entry:lens)
                         = [ label label_name . title
                           . (entry | comment)* ]

let record_noempty_setcomment (label_name:string) (entry:lens)
          (comment:lens) = [ label label_name . title
                           . (entry | comment)* ]

(************************************************************************
 *                              LENS
 *************************************************************************)

(* Generic INI file lens *)
let lns (record:lens)         = ( comment | empty )* . record*

(* Let the user choose the type of comment they want *)
let lns_setcomment (record:lens) (comment:lens)
                              = ( comment | empty )* . record*

(* Some implementations of INI File do not allow empty lines *)
let lns_noempty (record:lens) = comment* . record*

(* Let the user choose the type of comment they want *)
let lns_noempty_setcomment (record:lens) (comment:lens)
                              = comment* . record*

(* IniFile generic module for Augeas          *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(*                                            *)
(* TODO: Support double quotes in value       *)

module IniFile  =

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

let eol                  = Util.eol

let sep_gen (pat:regexp) (default:string)
                       = Util.del_opt_ws " " . del pat default
let value_sep          = sep_gen "=" "="
let value_sepwithcolon = sep_gen /[:=]/ "="

let value_to_eol       = Util.del_opt_ws " "
                         . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/
let value_to_comment   = Util.del_opt_ws " "
                         . store /[^;# \t\n][^;#\n]*[^;# \t\n]|[^;# \t\n]/

(* Define comment and empty strings *)
(* Some implementations of INI file allow "#" as a comment sign *)

let comment_generic (pat:regexp) (default:string)
                         = [ label "comment" . sep_gen pat default
			   .  value_to_eol . eol ]
let comment              = comment_generic /[#;]/ ";"
let comment_nosharp      = comment_generic ";" ";"

let empty                = Util.empty

(************************************************************************
 *                             ENTRY
 *************************************************************************)

(* Some implementations of INI file allow ";" as separator *)
let entry_generic (kw:regexp) (sep:lens) (comment:lens)
                    = [ key kw . sep . value_to_comment? . (comment|eol) ]
let entry (kw:regexp)
                    = entry_generic kw value_sepwithcolon comment
let entry_setcomment (kw:regexp) (comment:lens)
                    = entry_generic kw value_sepwithcolon comment
let entry_nocolon (kw:regexp)
                    = entry_generic kw value_sep comment
let entry_nocolon_setcomment (kw:regexp) (comment:lens)
                    = entry_generic kw value_sep comment

(************************************************************************
 *                             RECORD
 *************************************************************************)

let title                = Util.del_str "[" . store /[^]]+/
                           . Util.del_str "]". eol

let record (label_name:string) (entry:lens)
                         = [ label label_name  . title
			   . (entry | comment | empty)* ]

let record_setcomment (label_name:string) (entry:lens)
          (comment:lens) = [ label label_name . title
                           . (entry | comment | empty)* ]

(* Some implementations of INI File do not allow empty lines *)
let record_noempty (label_name:string) (entry:lens)
                         = [ label label_name . title
                           . (entry | comment)* ]

let record_noempty_setcomment (label_name:string) (entry:lens)
          (comment:lens) = [ label label_name . title
                           . (entry | comment)* ]

(************************************************************************
 *                              LENS
 *************************************************************************)

(* Generic INI file lens *)
let lns (record:lens)         = ( comment | empty )* . record*

(* Let the user choose the type of comment they want *)
let lns_setcomment (record:lens) (comment:lens)
                              = ( comment | empty )* . record*

(* Some implementations of INI File do not allow empty lines *)
let lns_noempty (record:lens) = comment* . record*

(* Let the user choose the type of comment they want *)
let lns_noempty_setcomment (record:lens) (comment:lens)
                              = comment* . record*