Sudoers: allow 'visiblepw' parameter flag

Fix provided by christj. Fixes bug #143

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

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

For example, recursive definitions such as

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

are replaced by

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

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

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

About: Lens Usage
  Sample usage of this lens in augtool

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

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

module Sudoers =
  autoload xfm

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

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = del /[ \t]*\n/ "\n"

(* Variable: indent *)
let indent    = del /[ \t]*/ ""

(* Group: Separators *)

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

(* Variable: sep_cont *)
let sep_cont = del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_cont_opt *)
let sep_cont_opt = del /([ \t]*|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_com *)
let sep_com  = sep_cont_opt . Util.del_str "," . sep_cont_opt

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt . Util.del_str "=" . sep_cont_opt

(* Variable: sep_col *)
let sep_col  = sep_cont_opt . Util.del_str ":" . sep_cont_opt

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

(* Group: Stores *)

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

(* Variable: sto_to_com

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

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

(* Variable: sto_to_com_user
Escaped spaces are allowed *)
let sto_to_com_user = store ( /([^,=:#() \t\n]([^,=:#() \t\n]|(\\\\[ \t]))*[^,=:#() \t\n])|[^,=:#() \t\n]/
                              - /(User|Runas|Host|Cmnd)_Alias|Defaults.*/ )

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

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

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

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

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

(* Group: Comments and empty lines *)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

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

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

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

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

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

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

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

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

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

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

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

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

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

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list>
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')'
 *************************************************************************)
let runas_spec = Util.del_str "(" . alias_list "runas_user" sto_to_com
    . Util.del_str ")" . sep_cont_opt

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

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

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

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

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

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

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

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

let xfm = transform lns filter

(*
Module: Sudoers
  Parses /etc/sudoers

Author: Raphael Pinson <raphink@gmail.com>

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

For example, recursive definitions such as

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

are replaced by

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

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

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

About: Lens Usage
  Sample usage of this lens in augtool

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

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

module Sudoers =
  autoload xfm

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

(* Group: Generic primitives *)
(* Variable: eol *)
let eol       = del /[ \t]*\n/ "\n"

(* Variable: indent *)
let indent    = del /[ \t]*/ ""

(* Group: Separators *)

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

(* Variable: sep_cont *)
let sep_cont = del /([ \t]+|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_cont_opt *)
let sep_cont_opt = del /([ \t]*|[ \t]*\\\\\n[ \t]*)/ " "

(* Variable: sep_com *)
let sep_com  = sep_cont_opt . Util.del_str "," . sep_cont_opt

(* Variable: sep_eq *)
let sep_eq   = sep_cont_opt . Util.del_str "=" . sep_cont_opt

(* Variable: sep_col *)
let sep_col  = sep_cont_opt . Util.del_str ":" . sep_cont_opt

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

(* Group: Stores *)

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

(* Variable: sto_to_com

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

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

(* Variable: sto_to_com_user
Escaped spaces are allowed *)
let sto_to_com_user = store ( /([^,=:#() \t\n]([^,=:#() \t\n]|(\\\\[ \t]))*[^,=:#() \t\n])|[^,=:#() \t\n]/
                              - /(User|Runas|Host|Cmnd)_Alias|Defaults.*/ )

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

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

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

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

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

(* Group: Comments and empty lines *)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let parameter_integer           = parameter_integer_nobool
                                | parameter_integer_bool

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

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

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

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

let parameter_string           = parameter_string_nobool
                               | parameter_string_bool

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

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

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

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

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

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

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

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

(************************************************************************
 * View: runas_spec
 *   A runas specification for <spec>, using <alias_list>
 *
 *   Definition:
 *     > Runas_Spec ::= '(' Runas_List ')'
 *************************************************************************)
let runas_spec = Util.del_str "(" . alias_list "runas_user" sto_to_com
    . Util.del_str ")" . sep_cont_opt

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

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

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

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

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

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

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

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

let xfm = transform lns filter


Improve documentation for sep.aug

Improve documentation for sep.aug

(*
Module: Sep
   Generic separators to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Sep =

(* Variable: colon *)
let colon = Util.del_str ":"

(* Variable: comma *)
let comma = Util.del_str ","

(* Variable: equal *)
let equal = Util.del_str "="

(* Variable: space
   Deletes a <Rx.space> and default to a single space *)
let space = del Rx.space " "

(* Variable: tab
   Deletes a <Rx.space> and default to a tab *)
let tab   = del Rx.space "\t"

(* Variable: opt_space
   Deletes a <Rx.opt_space> and default to an empty string *)
let opt_space = del Rx.opt_space ""

(* Variable: opt_tab
   Deletes a <Rx.opt_space> and default to a tab *)
let opt_tab   = del Rx.opt_space "\t"

(*
Module: Sep
   Generic separators to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Sep =

let colon = Util.del_str ":"
let comma = Util.del_str ","
let equal = Util.del_str "="
let space = del Rx.space " "
let tab   = del Rx.space "\t"
let opt_space = del Rx.opt_space ""
let opt_tab   = del Rx.opt_space "\t"


Resolv: no functional change, whitespace cleanup

(*
Module: Resolv
  Parses /etc/resolv.conf

Author: Raphael Pinson <raphink@gmail.com>

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

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

About: Lens Usage

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

module Resolv =
autoload xfm

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

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

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

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

(************************************************************************
 * Group:                 MAIN OPTIONS
 *************************************************************************)

(* View: netmask
A network mask for IP addresses *)
let netmask = [ label "netmask" . Util.del_str "/" . store Rx.ip ]

(* View: ipaddr
An IP address or range with an optional mask *)
let ipaddr = [label "ipaddr" . store Rx.ip . netmask?]

(* View: nameserver
     A nameserver entry *)
let nameserver = Build.key_value_line_comment
                    "nameserver" Sep.space (store Rx.ip) comment_eol

(* View: domain *)
let domain = Build.key_value_line_comment
                    "domain" Sep.space (store Rx.word) comment_eol

(* View: search *)
let search = Build.key_value_line_comment
                    "search" Sep.space
                    (Build.opt_list
                           [label "domain" . store Rx.word]
                            Sep.space)
                    comment_eol

(* View: sortlist *)
let sortlist = Build.key_value_line_comment
                    "sortlist" Sep.space
                    (Build.opt_list
                           ipaddr
                           Sep.space)
                    comment_eol

(* View: lookup *)
let lookup =
  let lookup_entry = Build.flag("bind"|"file"|"yp")
    in Build.key_value_line_comment
             "lookup" Sep.space
             (Build.opt_list
                    lookup_entry
                    Sep.space)
             comment_eol

(* View: family *)
let family =
  let family_entry = Build.flag("inet4"|"inet6")
    in Build.key_value_line_comment
             "family" Sep.space
             (Build.opt_list
                    family_entry
                    Sep.space)
             comment_eol

(************************************************************************
 * Group:                 SPECIAL OPTIONS
 *************************************************************************)

(* View: ip6_dotint
     ip6-dotint option, which supports negation *)
let ip6_dotint =
  let negate = [ del "no-" "no-" . label "negate" ]
    in [ negate? . key "ip6-dotint" ]

(* View: options
     Options values *)
let options =
      let options_entry = Build.key_value ("ndots"|"timeout"|"attempts")
                                          (Util.del_str ":") (store Rx.integer)
                        | Build.flag ("debug"|"rotate"|"no-check-names"
                                     |"inet6"|"ip6-bytestring"|"edns0"
				     |"single-request-reopen")
                        | ip6_dotint

            in Build.key_value_line_comment
                    "options" Sep.space
                    (Build.opt_list
                           options_entry
                           Sep.space)
                    comment_eol

(* View: entry *)
let entry = nameserver
          | domain
          | search
          | sortlist
          | options
          | lookup
          | family

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

(* Variable: filter *)
let filter = (incl "/etc/resolv.conf")

let xfm = transform lns filter


feat(lenses): add /etc/redis.conf to Redis.lns (#726)

Signed-off-by: Raphaël Pinson <0909132c360f4744d45149034acd6a9d36df8f8f@camptocamp.com>

(*
Module: Redis
  Parses Redis's configuration files

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

About: Reference
    This lens is based on Redis's default redis.conf

About: Usage Example
(start code)
augtool> set /augeas/load/Redis/incl "/etc/redis/redis.conf"
augtool> set /augeas/load/Redis/lens "Redis.lns"
augtool> load

augtool> get /files/etc/redis/redis.conf/vm-enabled
/files/etc/redis/redis.conf/vm-enabled = no
augtool> print /files/etc/redis/redis.conf/rename-command[1]/
/files/etc/redis/redis.conf/rename-command
/files/etc/redis/redis.conf/rename-command/from = "CONFIG"
/files/etc/redis/redis.conf/rename-command/to = "CONFIG2"

augtool> set /files/etc/redis/redis.conf/activerehashing no
augtool> save
Saved 1 file(s)
augtool> set /files/etc/redis/redis.conf/save[1]/seconds 123
augtool> set /files/etc/redis/redis.conf/save[1]/keys 456
augtool> save
Saved 1 file(s)
(end code)
   The <Test_Redis> file also contains various examples.

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

module Redis =
autoload xfm

let k = Rx.word
let v = /[^ \t\n'"]+/
let comment = Util.comment
let empty = Util.empty
let indent = Util.indent
let eol = Util.eol
let del_ws_spc = Util.del_ws_spc
let dquote = Util.del_str "\""

(* View: standard_entry
A standard entry is a key-value pair, separated by blank space, with optional
blank spaces at line beginning & end. The value part can be optionnaly enclosed
in single or double quotes. Comments at end-of-line ar NOT allowed by
redis-server.
*)
let standard_entry =
     let reserved_k = "save" | "rename-command" | "slaveof"
                    | "bind" | "client-output-buffer-limit"
  in let entry_noempty = [ indent . key (k - reserved_k) . del_ws_spc
                         . Quote.do_quote_opt_nil (store v) . eol ]
  in let entry_empty = [ indent . key (k - reserved_k) . del_ws_spc
                         . dquote . store "" . dquote . eol ]
  in entry_noempty | entry_empty

let save = /save/
let seconds = [ label "seconds" . Quote.do_quote_opt_nil (store Rx.integer) ]
let keys = [ label "keys" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: save_entry
Entries identified by the "save" keyword can be found more than once. They have
2 mandatory parameters, both integers. The same rules as standard_entry apply
for quoting, comments and whitespaces.
*)
let save_entry = [ indent . key save . del_ws_spc . seconds . del_ws_spc . keys . eol ]

let slaveof = /slaveof/
let ip = [ label "ip" . Quote.do_quote_opt_nil (store Rx.ip) ]
let port = [ label "port" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: slaveof_entry
Entries identified by the "slaveof" keyword can be found more than once. They
have 2 mandatory parameters, the 1st one is an IP address, the 2nd one is a
port number. The same rules as standard_entry apply for quoting, comments and
whitespaces.
*)
let slaveof_entry = [ indent . key slaveof . del_ws_spc . ip . del_ws_spc . port . eol ]

(* View: bind_entry
The "bind" entry can be passed one or several ip addresses. A bind
statement "bind ip1 ip2 .. ipn" results in a tree
{ "bind" { "ip" = ip1 } { "ip" = ip2 } ... { "ip" = ipn } }
*)
let bind_entry =
  let ip = del_ws_spc . Quote.do_quote_opt_nil (store Rx.ip) in
  indent . [ key "bind" . [ label "ip" . ip ]+ ] . eol

let renamecmd = /rename-command/
let from = [ label "from" . Quote.do_quote_opt_nil (store Rx.word) ]
let to = [ label "to" . Quote.do_quote_opt_nil (store Rx.word) ]
(* View: save_entry
Entries identified by the "rename-command" keyword can be found more than once.
They have 2 mandatory parameters, both strings. The same rules as
standard_entry apply for quoting, comments and whitespaces.
*)
let renamecmd_entry = [ indent . key renamecmd . del_ws_spc . from . del_ws_spc . to . eol ]

let cobl_cmd = /client-output-buffer-limit/
let class = [ label "class" . Quote.do_quote_opt_nil (store Rx.word) ]
let hard_limit = [ label "hard_limit" . Quote.do_quote_opt_nil (store Rx.word) ]
let soft_limit = [ label "soft_limit" . Quote.do_quote_opt_nil (store Rx.word) ]
let soft_seconds = [ label "soft_seconds" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: client_output_buffer_limit_entry
Entries identified by the "client-output-buffer-limit" keyword can be found
more than once. They have four mandatory parameters, of which the first is a
string, the last one is an integer and the others are either integers or words,
although redis is very liberal and takes "4242yadayadabytes" as a valid limit.
The same rules as standard_entry apply for quoting, comments and whitespaces.
*)
let client_output_buffer_limit_entry =
  [ indent . key cobl_cmd . del_ws_spc . class . del_ws_spc . hard_limit .
    del_ws_spc . soft_limit . del_ws_spc . soft_seconds . eol ]

let entry = standard_entry
          | save_entry
	  | renamecmd_entry
	  | slaveof_entry
	  | bind_entry
	  | client_output_buffer_limit_entry

(* View: lns
The Redis lens
*)
let lns = (comment | empty | entry )*

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

let xfm = transform lns filter

(*
Module: Redis
  Parses Redis's configuration files

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

About: Reference
    This lens is based on Redis's default redis.conf

About: Usage Example
(start code)
augtool> set /augeas/load/Redis/incl "/etc/redis/redis.conf"
augtool> set /augeas/load/Redis/lens "Redis.lns"
augtool> load

augtool> get /files/etc/redis/redis.conf/vm-enabled
/files/etc/redis/redis.conf/vm-enabled = no
augtool> print /files/etc/redis/redis.conf/rename-command[1]/
/files/etc/redis/redis.conf/rename-command
/files/etc/redis/redis.conf/rename-command/from = "CONFIG"
/files/etc/redis/redis.conf/rename-command/to = "CONFIG2"

augtool> set /files/etc/redis/redis.conf/activerehashing no
augtool> save
Saved 1 file(s)
augtool> set /files/etc/redis/redis.conf/save[1]/seconds 123
augtool> set /files/etc/redis/redis.conf/save[1]/keys 456
augtool> save
Saved 1 file(s)
(end code)
   The <Test_Redis> file also contains various examples.

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

module Redis =
autoload xfm

let k = Rx.word
let v = /[^ \t\n'"]+/
let comment = Util.comment
let empty = Util.empty
let indent = Util.indent
let eol = Util.eol
let del_ws_spc = Util.del_ws_spc
let dquote = Util.del_str "\""

(* View: standard_entry
A standard entry is a key-value pair, separated by blank space, with optional
blank spaces at line beginning & end. The value part can be optionnaly enclosed
in single or double quotes. Comments at end-of-line ar NOT allowed by
redis-server.
*)
let standard_entry =
     let reserved_k = "save" | "rename-command" | "slaveof"
                    | "bind" | "client-output-buffer-limit"
  in let entry_noempty = [ indent . key (k - reserved_k) . del_ws_spc
                         . Quote.do_quote_opt_nil (store v) . eol ]
  in let entry_empty = [ indent . key (k - reserved_k) . del_ws_spc
                         . dquote . store "" . dquote . eol ]
  in entry_noempty | entry_empty

let save = /save/
let seconds = [ label "seconds" . Quote.do_quote_opt_nil (store Rx.integer) ]
let keys = [ label "keys" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: save_entry
Entries identified by the "save" keyword can be found more than once. They have
2 mandatory parameters, both integers. The same rules as standard_entry apply
for quoting, comments and whitespaces.
*)
let save_entry = [ indent . key save . del_ws_spc . seconds . del_ws_spc . keys . eol ]

let slaveof = /slaveof/
let ip = [ label "ip" . Quote.do_quote_opt_nil (store Rx.ip) ]
let port = [ label "port" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: slaveof_entry
Entries identified by the "slaveof" keyword can be found more than once. They
have 2 mandatory parameters, the 1st one is an IP address, the 2nd one is a
port number. The same rules as standard_entry apply for quoting, comments and
whitespaces.
*)
let slaveof_entry = [ indent . key slaveof . del_ws_spc . ip . del_ws_spc . port . eol ]

(* View: bind_entry
The "bind" entry can be passed one or several ip addresses. A bind
statement "bind ip1 ip2 .. ipn" results in a tree
{ "bind" { "ip" = ip1 } { "ip" = ip2 } ... { "ip" = ipn } }
*)
let bind_entry =
  let ip = del_ws_spc . Quote.do_quote_opt_nil (store Rx.ip) in
  indent . [ key "bind" . [ label "ip" . ip ]+ ] . eol

let renamecmd = /rename-command/
let from = [ label "from" . Quote.do_quote_opt_nil (store Rx.word) ]
let to = [ label "to" . Quote.do_quote_opt_nil (store Rx.word) ]
(* View: save_entry
Entries identified by the "rename-command" keyword can be found more than once.
They have 2 mandatory parameters, both strings. The same rules as
standard_entry apply for quoting, comments and whitespaces.
*)
let renamecmd_entry = [ indent . key renamecmd . del_ws_spc . from . del_ws_spc . to . eol ]

let cobl_cmd = /client-output-buffer-limit/
let class = [ label "class" . Quote.do_quote_opt_nil (store Rx.word) ]
let hard_limit = [ label "hard_limit" . Quote.do_quote_opt_nil (store Rx.word) ]
let soft_limit = [ label "soft_limit" . Quote.do_quote_opt_nil (store Rx.word) ]
let soft_seconds = [ label "soft_seconds" . Quote.do_quote_opt_nil (store Rx.integer) ]
(* View: client_output_buffer_limit_entry
Entries identified by the "client-output-buffer-limit" keyword can be found
more than once. They have four mandatory parameters, of which the first is a
string, the last one is an integer and the others are either integers or words,
although redis is very liberal and takes "4242yadayadabytes" as a valid limit.
The same rules as standard_entry apply for quoting, comments and whitespaces.
*)
let client_output_buffer_limit_entry =
  [ indent . key cobl_cmd . del_ws_spc . class . del_ws_spc . hard_limit .
    del_ws_spc . soft_limit . del_ws_spc . soft_seconds . eol ]

let entry = standard_entry
          | save_entry
	  | renamecmd_entry
	  | slaveof_entry
	  | bind_entry
	  | client_output_buffer_limit_entry

(* View: lns
The Redis lens
*)
let lns = (comment | empty | entry )*

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

let xfm = transform lns filter


Adapt augeas profile to handle negative int values.

Introducing keepalive_interval = -1 breaks to augeas lens. Fix the lens
by allowing signed ints in the regular expression.

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

module Libvirtd =
   autoload xfm

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

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

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

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

   (* Config entry grouped by function - same order as example config *)
   let network_entry = bool_entry "listen_tls"
                     | bool_entry "listen_tcp"
                     | str_entry "tls_port"
                     | str_entry "tcp_port"
                     | str_entry "listen_addr"
                     | bool_entry "mdns_adv"
                     | str_entry "mdns_name"

   let sock_acl_entry = str_entry "unix_sock_group"
                      | str_entry "unix_sock_ro_perms"
                      | str_entry "unix_sock_rw_perms"
                      | str_entry "unix_sock_admin_perms"
                      | str_entry "unix_sock_dir"

   let authentication_entry = str_entry "auth_unix_ro"
                            | str_entry "auth_unix_rw"
                            | str_entry "auth_tcp"
                            | str_entry "auth_tls"

   let certificate_entry = str_entry "key_file"
                         | str_entry "cert_file"
                         | str_entry "ca_file"
                         | str_entry "crl_file"

   let authorization_entry = bool_entry "tls_no_verify_certificate"
                           | bool_entry "tls_no_sanity_certificate"
                           | str_array_entry "tls_allowed_dn_list"
                           | str_array_entry "sasl_allowed_username_list"
                           | str_array_entry "access_drivers"

   let processing_entry = int_entry "min_workers"
                        | int_entry "max_workers"
                        | int_entry "max_clients"
                        | int_entry "max_queued_clients"
                        | int_entry "max_anonymous_clients"
                        | int_entry "max_requests"
                        | int_entry "max_client_requests"
                        | int_entry "prio_workers"

   let admin_processing_entry = int_entry "admin_min_workers"
                              | int_entry "admin_max_workers"
                              | int_entry "admin_max_clients"
                              | int_entry "admin_max_queued_clients"
                              | int_entry "admin_max_client_requests"

   let logging_entry = int_entry "log_level"
                     | str_entry "log_filters"
                     | str_entry "log_outputs"
                     | int_entry "log_buffer_size"

   let auditing_entry = int_entry "audit_level"
                      | bool_entry "audit_logging"

   let keepalive_entry = int_entry "keepalive_interval"
                       | int_entry "keepalive_count"
                       | bool_entry "keepalive_required"

   let admin_keepalive_entry = int_entry "admin_keepalive_interval"
                             | int_entry "admin_keepalive_count"
                             | bool_entry "admin_keepalive_required"

   let misc_entry = str_entry "host_uuid"

   (* Each enty in the config is one of the following three ... *)
   let entry = network_entry
             | sock_acl_entry
             | authentication_entry
             | certificate_entry
             | authorization_entry
             | processing_entry
             | admin_processing_entry
             | logging_entry
             | auditing_entry
             | keepalive_entry
             | admin_keepalive_entry
             | misc_entry
   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
   let empty = [ label "#empty" . eol ]

   let record = indent . entry . eol

   let lns = ( record | comment | empty ) *

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

   let xfm = transform lns filter

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

module Libvirtd =
   autoload xfm

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

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

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

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

   (* Config entry grouped by function - same order as example config *)
   let network_entry = bool_entry "listen_tls"
                     | bool_entry "listen_tcp"
                     | str_entry "tls_port"
                     | str_entry "tcp_port"
                     | str_entry "listen_addr"
                     | bool_entry "mdns_adv"
                     | str_entry "mdns_name"

   let sock_acl_entry = str_entry "unix_sock_group"
                      | str_entry "unix_sock_ro_perms"
                      | str_entry "unix_sock_rw_perms"
                      | str_entry "unix_sock_admin_perms"
                      | str_entry "unix_sock_dir"

   let authentication_entry = str_entry "auth_unix_ro"
                            | str_entry "auth_unix_rw"
                            | str_entry "auth_tcp"
                            | str_entry "auth_tls"

   let certificate_entry = str_entry "key_file"
                         | str_entry "cert_file"
                         | str_entry "ca_file"
                         | str_entry "crl_file"

   let authorization_entry = bool_entry "tls_no_verify_certificate"
                           | bool_entry "tls_no_sanity_certificate"
                           | str_array_entry "tls_allowed_dn_list"
                           | str_array_entry "sasl_allowed_username_list"
                           | str_array_entry "access_drivers"

   let processing_entry = int_entry "min_workers"
                        | int_entry "max_workers"
                        | int_entry "max_clients"
                        | int_entry "max_queued_clients"
                        | int_entry "max_anonymous_clients"
                        | int_entry "max_requests"
                        | int_entry "max_client_requests"
                        | int_entry "prio_workers"

   let admin_processing_entry = int_entry "admin_min_workers"
                              | int_entry "admin_max_workers"
                              | int_entry "admin_max_clients"
                              | int_entry "admin_max_queued_clients"
                              | int_entry "admin_max_client_requests"

   let logging_entry = int_entry "log_level"
                     | str_entry "log_filters"
                     | str_entry "log_outputs"
                     | int_entry "log_buffer_size"

   let auditing_entry = int_entry "audit_level"
                      | bool_entry "audit_logging"

   let keepalive_entry = int_entry "keepalive_interval"
                       | int_entry "keepalive_count"
                       | bool_entry "keepalive_required"

   let admin_keepalive_entry = int_entry "admin_keepalive_interval"
                             | int_entry "admin_keepalive_count"
                             | bool_entry "admin_keepalive_required"

   let misc_entry = str_entry "host_uuid"

   (* Each enty in the config is one of the following three ... *)
   let entry = network_entry
             | sock_acl_entry
             | authentication_entry
             | certificate_entry
             | authorization_entry
             | processing_entry
             | admin_processing_entry
             | logging_entry
             | auditing_entry
             | keepalive_entry
             | admin_keepalive_entry
             | misc_entry
   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
   let empty = [ label "#empty" . eol ]

   let record = indent . entry . eol

   let lns = ( record | comment | empty ) *

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

   let xfm = transform lns filter


group: Testcase for NIS default

group: Testcase for NIS default

module Test_group =

let conf = "bin:x:2:
audio:x:29:joe
avahi-autoipd:!:113:bill,martha
"

test Group.lns get conf =
   { "bin"
     { "password" = "x" }
     { "gid" = "2" } }
   { "audio"
     { "password" = "x" }
     { "gid" = "29" }
     { "user" = "joe" } }
   { "avahi-autoipd"
     { "password" = "!" }
     { "gid" = "113" }
     { "user" = "bill"}
     { "user" = "martha"} }

(* Password field can be empty *)
test Group.lns get "root::0:root\n" =
  { "root"
    { "password" = "" }
    { "gid" = "0" }
    { "user" = "root" } }

(* Password field can be disabled by ! or * *)
test Group.lns get "testgrp:!:0:testusr\n" =
  { "testgrp"
    { "password" = "!" }
    { "gid" = "0" }
    { "user" = "testusr" } }

test Group.lns get "testgrp:*:0:testusr\n" =
  { "testgrp"
    { "password" = "*" }
    { "gid" = "0" }
    { "user" = "testusr" } }

(* NIS defaults *)
test Group.lns get "+\n" =
  { "@nisdefault" }

test Group.lns get "+:::\n" =
  { "@nisdefault"
    { "password" = "" }
    { "gid" = "" } }


Rework map lense as slashes are needed for keys

(*
Module: Sendmail
  Parses map files used by the Sendmail MTA

Author: Stefan Moeding <stm@kill-9.net>

About: Reference

About: License

About: Lens Usage

About: Configuration files
  This lens applies to /etc/mail/access, /etc/mail/authinfo,
  /etc/mail/bitdomain, /etc/mail/domaintable, /etc/mail/genericstable,
  /etc/mail/mailertable, /etc/mail/msp-authinfo, /etc/mail/userdb,
  /etc/mail/uudomain and /etc/mail/virtusertable.

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

module Sendmail_Map =
autoload xfm

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

let eol        = Util.eol
let empty      = Util.empty
let comment    = Util.comment
let del_ws_tab = Util.del_ws_tab

(************************************************************************
 * Group:                           VIEWS
 ************************************************************************)

(* View: key_re *)
let key_re = /[a-zA-Z0-9@.:_=\/+-]+/

(* View: val_re *)
let val_re = /[^ \t\n](.*[^ \t\n])?/

(* View: keyval *)
let keyval = [ seq "key" .
               [ label "key" . store key_re ] . del_ws_tab .
               [ label "value" . store val_re ] .
               eol ]

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

let lns    = ( comment | empty | keyval )*

let filter = incl "/etc/mail/access"
           . incl "/etc/mail/authinfo"
           . incl "/etc/mail/bitdomain"
           . incl "/etc/mail/domaintable"
           . incl "/etc/mail/genericstable"
           . incl "/etc/mail/mailertable"
           . incl "/etc/mail/msp-authinfo"
           . incl "/etc/mail/userdb"
           . incl "/etc/mail/uudomain"
           . incl "/etc/mail/virtusertable"
           . Util.stdexcl

let xfm    = transform lns filter

(*
Module: Sendmail
  Parses map files used by the Sendmail MTA

Author: Stefan Moeding <stm@kill-9.net>

About: Reference

About: License

About: Lens Usage

About: Configuration files
  This lens applies to /etc/mail/access, /etc/mail/authinfo,
  /etc/mail/bitdomain, /etc/mail/domaintable, /etc/mail/genericstable,
  /etc/mail/mailertable, /etc/mail/msp-authinfo, /etc/mail/userdb,
  /etc/mail/uudomain and /etc/mail/virtusertable.

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

module Sendmail_Map =
autoload xfm

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

let eol        = Util.eol
let empty      = Util.empty
let comment    = Util.comment
let del_ws_tab = Util.del_ws_tab

(************************************************************************
 * Group:                           VIEWS
 ************************************************************************)

(* View: key_re *)
let key_re = /[a-zA-Z0-9.@_:+-]+/

(* View: val_re *)
let val_re = /[^ \t\n](.*[^ \t\n])?/

(* View: keyval *)
let keyval = [ key key_re . del_ws_tab . store val_re . eol ]

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

let lns    = ( comment | empty | keyval )*

let filter = incl "/etc/mail/access"
           . incl "/etc/mail/authinfo"
           . incl "/etc/mail/bitdomain"
           . incl "/etc/mail/domaintable"
           . incl "/etc/mail/genericstable"
           . incl "/etc/mail/mailertable"
           . incl "/etc/mail/msp-authinfo"
           . incl "/etc/mail/userdb"
           . incl "/etc/mail/uudomain"
           . incl "/etc/mail/virtusertable"
           . Util.stdexcl

let xfm    = transform lns filter


Puppetfile: add support for moduledir (Christoph Maser)

(*
Module: Puppetfile
  Parses libarian-puppet's Puppetfile format

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

About: Reference
  See https://github.com/rodjek/librarian-puppet

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 Puppetfiles.

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

module Puppetfile =

(* View: comma
     a comma, optionally preceded or followed by spaces or newlines *)
let comma = del /[ \t\n]*,[ \t\n]*/ ", "

(* View: moduledir
     The moduledir setting specifies where modules from the Puppetfile will be installed *)
let moduledir = [ Util.indent . key "moduledir" . Sep.space . Quote.any . Util.eol ]

(* View: forge
     a forge entry *)
let forge = [ Util.indent . key "forge" . Sep.space . Quote.any . Util.eol ]

(* View: metadata
     a metadata entry *)
let metadata = [ Util.indent . key "metadata" . Util.eol ]

(* View: mod
     a module entry, with optional version and options *)
let mod =
     let mod_name = Quote.do_quote (store ((Rx.word . /[\/-]/)? . Rx.word))
  in let version = [ label "@version" . Quote.do_quote (store /[^:\n]+/) ]
  in let opt = [ Util.del_str ":" . key Rx.word . del /[ \t]*=>[ \t]*/ " => "
               . Quote.do_quote (store /[^,\n]*/) ]
  in let opts = Build.opt_list opt comma
  in [ Util.indent . Util.del_str "mod" . seq "mod" . Sep.space . mod_name
     . (comma . version)?
     . (comma . opts)?
     . Util.eol ]

(* View: lns
     the Puppetfile lens *)
let lns = (Util.empty | Util.comment | forge | metadata | mod | moduledir )*

(*
Module: Puppetfile
  Parses libarian-puppet's Puppetfile format

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

About: Reference
  See https://github.com/rodjek/librarian-puppet

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 Puppetfiles.

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

module Puppetfile =

(* View: comma
     a comma, optionally preceded or followed by spaces or newlines *)
let comma = del /[ \t\n]*,[ \t\n]*/ ", "

(* View: forge
     a forge entry *)
let forge = [ Util.indent . key "forge" . Sep.space . Quote.any . Util.eol ]

(* View: metadata
     a metadata entry *)
let metadata = [ Util.indent . key "metadata" . Util.eol ]

(* View: mod
     a module entry, with optional version and options *)
let mod =
     let mod_name = Quote.do_quote (store ((Rx.word . /[\/-]/)? . Rx.word))
  in let version = [ label "@version" . Quote.do_quote (store /[^:\n]+/) ]
  in let opt = [ Util.del_str ":" . key Rx.word . del /[ \t]*=>[ \t]*/ " => "
               . Quote.do_quote (store /[^,\n]*/) ]
  in let opts = Build.opt_list opt comma
  in [ Util.indent . Util.del_str "mod" . seq "mod" . Sep.space . mod_name
     . (comma . version)?
     . (comma . opts)?
     . Util.eol ]

(* View: lns
     the Puppetfile lens *)
let lns = (Util.empty | Util.comment | forge | metadata | mod)*


Also handle /etc/monitrc

Also handle /etc/monitrc

Fixes ticket #326

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

 Reference: man monit (1), section "HOW TO MONITOR"

 "A monit control file consists of a series of service entries and
  global option statements in a free-format, token-oriented syntax.

  Comments begin with a # and extend through the end of the line. There
  are three kinds of tokens in the control file: grammar keywords, numbers
  and strings. On a semantic level, the control file consists of three
  types of statements:

  1. Global set-statements
      A global set-statement starts with the keyword set and the item to
      configure.

  2. Global include-statement
      The include statement consists of the keyword include and a glob
      string.

  3. One or more service entry statements.
       A service entry starts with the keyword check followed by the
       service type"

*)

module Monit =

   autoload xfm

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

let spc        = Sep.space
let comment    = Util.comment
let empty      = Util.empty

let sto_to_spc = store Rx.space_in
let sto_no_spc = store Rx.no_spaces

let word       = Rx.word
let value      = Build.key_value_line word spc sto_to_spc

(************************************************************************
 *                               ENTRIES
 *************************************************************************)

(* set statement *)
let set        = Build.key_value "set" spc value

(* include statement *)
let include    = Build.key_value_line "include" spc sto_to_spc

(* service statement *)
let service    = Build.key_value "check" spc (Build.list value spc)

let entry      = (set|include|service)

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

let lns        = (comment|empty|entry) *

let filter     = incl "/etc/monit/monitrc"
               . incl "/etc/monitrc"

let xfm        = transform lns filter

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

 Reference: man monit (1), section "HOW TO MONITOR"

 "A monit control file consists of a series of service entries and
  global option statements in a free-format, token-oriented syntax.

  Comments begin with a # and extend through the end of the line. There
  are three kinds of tokens in the control file: grammar keywords, numbers
  and strings. On a semantic level, the control file consists of three
  types of statements:

  1. Global set-statements
      A global set-statement starts with the keyword set and the item to
      configure.

  2. Global include-statement
      The include statement consists of the keyword include and a glob
      string.

  3. One or more service entry statements.
       A service entry starts with the keyword check followed by the
       service type"

*)

module Monit =

   autoload xfm

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

let spc        = Sep.space
let comment    = Util.comment
let empty      = Util.empty

let sto_to_spc = store Rx.space_in
let sto_no_spc = store Rx.no_spaces

let word       = Rx.word
let value      = Build.key_value_line word spc sto_to_spc

(************************************************************************
 *                               ENTRIES
 *************************************************************************)

(* set statement *)
let set        = Build.key_value "set" spc value

(* include statement *)
let include    = Build.key_value_line "include" spc sto_to_spc

(* service statement *)
let service    = Build.key_value "check" spc (Build.list value spc)

let entry      = (set|include|service)

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

let lns        = (comment|empty|entry) *

let filter     = incl "/etc/monit/monitrc"

let xfm        = transform lns filter


Improve documentation of build.aug

Improve documentation of build.aug

(*
Module: Build
   Generic functions to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Build =

let eol = Util.eol

(* Generic constructions *)
let brackets (l:lens) (r:lens) (lns:lens) = l . lns . r

(* List constructions *)
let list (lns:lens) (sep:lens) = lns . ( sep . lns )+
let opt_list (lns:lens) (sep:lens) = lns . ( sep . lns )*

(* Labels *)
let xchg (m:regexp) (d:string) (l:string) = del m d . label l

let xchgs (m:string) (l:string) = xchg m m l

(* Keys *)
let key_value_line (kw: regexp) (sep:lens) (sto:lens) =
                                   [ key kw . sep . sto . eol ]

let key_value (kw: regexp) (sep:lens) (sto:lens) =
                                   [ key kw . sep . sto ]


Mysql: change default comment delimiter from ';' to '#'

Fixes bug #181 reported by devzero

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

module MySQL =
  autoload xfm

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

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

let entry    = [ key IniFile.entry_re . sep . IniFile.sto_to_comment . (comment|IniFile.eol) ] |
               [ key IniFile.entry_re . store // . (comment|IniFile.eol) ] |
               [ key /\![A-Za-z][A-Za-z0-9\._-]+/ . del / / " " . store /\/[A-Za-z0-9\.\/_-]+/ . (comment|IniFile.eol) ] |
               comment

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

let lns    = IniFile.lns record comment

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

let xfm = transform lns filter

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

module MySQL =
  autoload xfm

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

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

let entry    = [ key IniFile.entry_re . sep . IniFile.sto_to_comment . (comment|IniFile.eol) ] |
               [ key IniFile.entry_re . store // . (comment|IniFile.eol) ] |
               [ key /\![A-Za-z][A-Za-z0-9\._-]+/ . del / / " " . store /\/[A-Za-z0-9\.\/_-]+/ . (comment|IniFile.eol) ] |
               comment

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

let lns    = IniFile.lns record comment

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

let xfm = transform lns filter


Added config.d files to the filter

Added config.d files to the filter

Used by systems running Red Hat or a variant

(* 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: block_re
     The keywords reserved for block entries *)
let block_re = "http" | "events"
             | "server" | "upstream"
             | "location" | "if"

(* View: simple
     A simple entry *)
let simple =
     let kw = Rx.word - block_re
  in let sto = store /[^ \t\n;][^;]*/ . Sep.semicolon
  in Build.key_value_line_comment
     kw Sep.space sto Util.comment_eol

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

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

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

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: block_re
     The keywords reserved for block entries *)
let block_re = "http" | "events"
             | "server" | "upstream"
             | "location" | "if"

(* View: simple
     A simple entry *)
let simple =
     let kw = Rx.word - block_re
  in let sto = store /[^ \t\n;][^;]*/ . Sep.semicolon
  in Build.key_value_line_comment
     kw Sep.space sto Util.comment_eol

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

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

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

let xfm = transform lns filter


M #-: Fix sinle entry section indent in oned.aug (#3809)

module Oned =
  autoload xfm

(* Version: 1.1 *)

(* primitives *)
let sep = del /[ \t]*=[ \t]*/ " = "
let eol = del /\n/ "\n"
let opt_space = del /[ \t]*/ ""
let opt_space_nl = del /[ \t\n]*/ "\n"
let opt_nl_indent = del /[ \t\n]*/ "\n    "
let comma = del /,/ ","
let left_br = del /\[/ "["
let right_br = del /\]/ "]"

(* Regexes *)
(* Match everyhting within quotes *)
let re_quoted_str = /"[^\"]*"/

(* Match everything except spaces, quote("), l-bracket([) and num-sign(#) *)
let re_value_str = /[^ \t\n"\[#]+/

(* Match everything except spaces, quote("), num-sign(#) and comma(,) *)
let re_section_value_str = /[^ \t\n"#,]+/

(* Store either after-value comment or full-line comment *)
let comment = [ label "#comment" . store /#[^\n]*/ ]
let comment_eol = comment . eol

(* Simple words *)
let name = key /[A-Za-z_0-9]+/
let re_simple_value = re_quoted_str | re_value_str

(* Top level entry like `PORT = 2633` *)
let top_level_entry  = name . sep . store re_simple_value
let top_level_line =  opt_space
                      . [ top_level_entry . opt_space . (comment)? ]
                      . eol

(* Section lens for section like `LOG = [ ... ]` *)
let section_value = re_quoted_str | re_section_value_str
let section_entry = [ name . sep . store section_value ]
let section_entry_list =
    ( section_entry . opt_space . comma . opt_nl_indent
      | comment_eol . opt_space )*
    . section_entry . opt_space_nl
    . ( comment_eol )*

let section = opt_space
              . [ name . sep
              . left_br
                . opt_nl_indent
                . section_entry_list
              . right_br ]
              . eol

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

(* Main lens *)
let lns = ( top_level_line | comment_eol | section | empty_line )*

(* Variable: filter *)
let filter = incl "/etc/one/oned.conf"
             . incl "/etc/one/sched.conf"
             . incl "/etc/one/vmm_exec/vmm_exec_kvm.conf"

let xfm = transform lns filter

module Oned =
  autoload xfm

(* primitives *)
let sep = del /[ \t]*=[ \t]*/ " = "
let eol = del /\n/ "\n"
let opt_space = del /[ \t]*/ ""
let opt_space_nl = del /[ \t\n]*/ "\n"
let opt_nl_indent = del /[ \t\n]*/ "\n    "
let comma = del /,/ ","
let left_br = del /\[/ "["
let right_br = del /\]/ "]"

(* Regexes *)
(* Match everyhting within quotes *)
let re_quoted_str = /"[^\"]*"/

(* Match everything except spaces, quote("), l-bracket([) and num-sign(#) *)
let re_value_str = /[^ \t\n"\[#]+/

(* Match everything except spaces, quote("), num-sign(#) and comma(,) *)
let re_section_value_str = /[^ \t\n"#,]+/

(* Store either after-value comment or full-line comment *)
let comment = [ label "#comment" . store /#[^\n]*/ ]
let comment_eol = comment . eol

(* Simple words *)
let name = key /[A-Za-z_0-9]+/
let re_simple_value = re_quoted_str | re_value_str

(* Top level entry like `PORT = 2633` *)
let top_level_entry  = name . sep . store re_simple_value
let top_level_line =  opt_space
                      . [ top_level_entry . opt_space . (comment)? ]
                      . eol

(* Section lens for section like `LOG = [ ... ]` *)
let section_value = re_quoted_str | re_section_value_str
let section_entry = [ name . sep . store section_value ]
let section_entry_list =
    ( section_entry . opt_space . comma . opt_nl_indent
      | comment_eol . opt_space )*
    . section_entry . opt_space_nl
    . ( comment_eol )*

let section = opt_space
              . [ name . sep
              . left_br
                . opt_space_nl
                . section_entry_list
              . right_br ]
              . eol

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

(* Main lens *)
let lns = ( top_level_line | comment_eol | section | empty_line )*

(* Variable: filter *)
let filter = incl "/etc/one/oned.conf"
             . incl "/etc/one/sched.conf"
             . incl "/etc/one/vmm_exec/vmm_exec_kvm.conf"

let xfm = transform lns filter


Nginx: Add non-recursive ambiguities test

(*
Module: Test_Nginx
  Provides unit tests and examples for the <Nginx> lens.
*)
module Test_nginx =

(* Check for non-recursive ambiguities *)
let directive = Nginx.simple
              | Nginx.block (
                   Nginx.simple
                 | Nginx.block Nginx.simple
              )

(* Do some limited typechecking on the recursive lens; note that
   unrolling once more leads to a typecheck error that seems to
   be spurious, though it's not clear why

   Unrolling once more amounts to adding the clause
      Nginx.block (Nginx.block Nginx.simple)
   to unrolled and results in an error
      overlapping lenses in union.get
        Example matched by both: 'upstream{}\n'
*)
let unrolled = Nginx.simple | Nginx.block Nginx.simple

let lns_unrolled = (Util.comment | Util.empty | unrolled)

(* Normal unit tests *)
let lns = Nginx.lns

let conf ="user nginx nginx;
worker_processes 1;
error_log /var/log/nginx/error_log info;

events {
        worker_connections 1024;
        use epoll;
}

# comment1
# comment2

http {
	# comment3
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        log_format main
                '$remote_addr - $remote_user [$time_local] '
                '\"$request\" $status $bytes_sent '
                '\"$http_referer\" \"$http_user_agent\" '
                '\"$gzip_ratio\"';
        client_header_timeout 10m;
        client_body_timeout 10m;
        send_timeout 10m;
        connection_pool_size 256;
        client_header_buffer_size 2k;
        large_client_header_buffers 4 8k;
        request_pool_size 4k;
        gzip on;
	gzip_min_length 1000;
        gzip_buffers 4 8k;
        gzip_types text/plain application/json;
        output_buffers 1 32k;
        postpone_output 1460;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 75 20;
        ignore_invalid_headers on;
        index index.html index.php;
        include vhosts/*.conf;
}
"

test lns get conf =
   { "user"  = "nginx nginx" }
   { "worker_processes"      = "1" }
   { "error_log" = "/var/log/nginx/error_log info" }
   {}
   { "events"
      { "worker_connections"  = "1024" }
      { "use"      = "epoll" } }
   {}
   { "#comment" = "comment1" }
   { "#comment" = "comment2" }
   {}
   { "http"
   	  { "#comment" = "comment3" }
      { "include"  = "/etc/nginx/mime.types" }
      { "default_type"  = "application/octet-stream" }
      { "log_format"      = "main
                '$remote_addr - $remote_user [$time_local] '
                '\"$request\" $status $bytes_sent '
                '\"$http_referer\" \"$http_user_agent\" '
                '\"$gzip_ratio\"'" }
      { "client_header_timeout"  = "10m" }
      { "client_body_timeout"  = "10m" }
      { "send_timeout"      = "10m" }
      { "connection_pool_size"      = "256" }
      { "client_header_buffer_size"      = "2k" }
      { "large_client_header_buffers"      = "4 8k" }
      { "request_pool_size"      = "4k" }
      { "gzip"      = "on" }
      { "gzip_min_length"      = "1000" }
      { "gzip_buffers"      = "4 8k" }
      { "gzip_types"      = "text/plain application/json" }
      { "output_buffers"      = "1 32k" }
      { "postpone_output"      = "1460" }
      { "sendfile"      = "on" }
      { "tcp_nopush"      = "on" }
      { "tcp_nodelay"      = "on" }
      { "keepalive_timeout"      = "75 20" }
      { "ignore_invalid_headers"      = "on" }
      { "index"      = "index.html index.php" }
      { "include"      = "vhosts/*.conf" } }

(* location blocks *)
test lns get "location / { }\n" =
  { "location"
    { "#uri" = "/" } }

test lns get "location = / { }\n" =
  { "location"
      { "#comp" = "=" }
      { "#uri" = "/" } }

test lns get "location /documents/ { }\n" =
  { "location"
    { "#uri" = "/documents/" } }

test lns get "location ^~ /images/ { }\n" =
  { "location"
    { "#comp" = "^~" }
    { "#uri" = "/images/" } }

test lns get "location ~* \.(gif|jpg|jpeg)$ { }\n" =
  { "location"
    { "#comp" = "~*" }
    { "#uri" = "\.(gif|jpg|jpeg)$" } }

test lns get "location @fallback { }\n" =
 { "location"
    { "#uri" = "@fallback" } }

(* if blocks *)
test lns get "if ($slow) {
  tcp_nodelay on;
}\n" =
  { "if"
    { "#cond" = "($slow)" }
    { "tcp_nodelay" = "on" } }

test lns get "if ($request_method = POST)   { }\n" =
 { "if"
    { "#cond" = "($request_method = POST)" } }

test lns get "if ($http_cookie ~* \"id=([^;]+)(?:;|$)\") { }\n" =
 { "if"
    { "#cond" = "($http_cookie ~* \"id=([^;]+)(?:;|$)\")" } }

(* geo blocks *)
test lns get "geo $geo { }\n" =
  { "geo"
    { "#geo" = "$geo" } }

test lns get "geo $address $geo { }\n" =
  { "geo"
    { "#address" = "$address" }
    { "#geo" = "$geo" } }

(* map blocks *)
test lns get "map $http_host $name { }\n" =
  { "map"
    { "#source" = "$http_host" }
    { "#variable" = "$name" } }

(* split_clients block *)
test lns get "split_clients \"${remote_addr}AAA\" $variable { }\n" =
  { "split_clients"
    { "#string" = "\"${remote_addr}AAA\"" }
    { "#variable" = "$variable" } }

(* upstream block *)
test lns get "upstream backend { }\n" =
 { "upstream"
    { "#name" = "backend" } }

(* GH #179 - recursive blocks *)
let http = "http {
  server {
    listen 80;
    location / {
      root\thtml;
    }
  }
  gzip on;
}\n"

test lns get http =
  { "http"
    { "server"
       { "listen" = "80" }
       { "location"
         { "#uri" = "/" }
         { "root" = "html" } } }
    { "gzip" = "on" } }

(* Make sure we do not screw up the indentation of the file *)
test lns put http after set "/http/gzip" "off" =
"http {
  server {
    listen 80;
    location / {
      root\thtml;
    }
  }
  gzip off;
}\n"

(*
Module: Test_Nginx
  Provides unit tests and examples for the <Nginx> lens.
*)
module Test_nginx =

(* Do some limited typechecking on the recursive lens; note that
   unrolling once more leads to a typecheck error that seems to
   be spurious, though it's not clear why

   Unrolling once more amounts to adding the clause
      Nginx.block (Nginx.block Nginx.simple)
   to unrolled and results in an error
      overlapping lenses in union.get
        Example matched by both: 'upstream{}\n'
*)
let unrolled = Nginx.simple | Nginx.block Nginx.simple

let lns_unrolled = (Util.comment | Util.empty | unrolled)

(* Normal unit tests *)
let lns = Nginx.lns

let conf ="user nginx nginx;
worker_processes 1;
error_log /var/log/nginx/error_log info;

events {
        worker_connections 1024;
        use epoll;
}

# comment1
# comment2

http {
	# comment3
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        log_format main
                '$remote_addr - $remote_user [$time_local] '
                '\"$request\" $status $bytes_sent '
                '\"$http_referer\" \"$http_user_agent\" '
                '\"$gzip_ratio\"';
        client_header_timeout 10m;
        client_body_timeout 10m;
        send_timeout 10m;
        connection_pool_size 256;
        client_header_buffer_size 2k;
        large_client_header_buffers 4 8k;
        request_pool_size 4k;
        gzip on;
	gzip_min_length 1000;
        gzip_buffers 4 8k;
        gzip_types text/plain application/json;
        output_buffers 1 32k;
        postpone_output 1460;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 75 20;
        ignore_invalid_headers on;
        index index.html index.php;
        include vhosts/*.conf;
}
"

test lns get conf =
   { "user"  = "nginx nginx" }
   { "worker_processes"      = "1" }
   { "error_log" = "/var/log/nginx/error_log info" }
   {}
   { "events"
      { "worker_connections"  = "1024" }
      { "use"      = "epoll" } }
   {}
   { "#comment" = "comment1" }
   { "#comment" = "comment2" }
   {}
   { "http"
   	  { "#comment" = "comment3" }
      { "include"  = "/etc/nginx/mime.types" }
      { "default_type"  = "application/octet-stream" }
      { "log_format"      = "main
                '$remote_addr - $remote_user [$time_local] '
                '\"$request\" $status $bytes_sent '
                '\"$http_referer\" \"$http_user_agent\" '
                '\"$gzip_ratio\"'" }
      { "client_header_timeout"  = "10m" }
      { "client_body_timeout"  = "10m" }
      { "send_timeout"      = "10m" }
      { "connection_pool_size"      = "256" }
      { "client_header_buffer_size"      = "2k" }
      { "large_client_header_buffers"      = "4 8k" }
      { "request_pool_size"      = "4k" }
      { "gzip"      = "on" }
      { "gzip_min_length"      = "1000" }
      { "gzip_buffers"      = "4 8k" }
      { "gzip_types"      = "text/plain application/json" }
      { "output_buffers"      = "1 32k" }
      { "postpone_output"      = "1460" }
      { "sendfile"      = "on" }
      { "tcp_nopush"      = "on" }
      { "tcp_nodelay"      = "on" }
      { "keepalive_timeout"      = "75 20" }
      { "ignore_invalid_headers"      = "on" }
      { "index"      = "index.html index.php" }
      { "include"      = "vhosts/*.conf" } }

(* location blocks *)
test lns get "location / { }\n" =
  { "location"
    { "#uri" = "/" } }

test lns get "location = / { }\n" =
  { "location"
      { "#comp" = "=" }
      { "#uri" = "/" } }

test lns get "location /documents/ { }\n" =
  { "location"
    { "#uri" = "/documents/" } }

test lns get "location ^~ /images/ { }\n" =
  { "location"
    { "#comp" = "^~" }
    { "#uri" = "/images/" } }

test lns get "location ~* \.(gif|jpg|jpeg)$ { }\n" =
  { "location"
    { "#comp" = "~*" }
    { "#uri" = "\.(gif|jpg|jpeg)$" } }

test lns get "location @fallback { }\n" =
 { "location"
    { "#uri" = "@fallback" } }

(* if blocks *)
test lns get "if ($slow) {
  tcp_nodelay on;
}\n" =
  { "if"
    { "#cond" = "($slow)" }
    { "tcp_nodelay" = "on" } }

test lns get "if ($request_method = POST)   { }\n" =
 { "if"
    { "#cond" = "($request_method = POST)" } }

test lns get "if ($http_cookie ~* \"id=([^;]+)(?:;|$)\") { }\n" =
 { "if"
    { "#cond" = "($http_cookie ~* \"id=([^;]+)(?:;|$)\")" } }

(* geo blocks *)
test lns get "geo $geo { }\n" =
  { "geo"
    { "#geo" = "$geo" } }

test lns get "geo $address $geo { }\n" =
  { "geo"
    { "#address" = "$address" }
    { "#geo" = "$geo" } }

(* map blocks *)
test lns get "map $http_host $name { }\n" =
  { "map"
    { "#source" = "$http_host" }
    { "#variable" = "$name" } }

(* split_clients block *)
test lns get "split_clients \"${remote_addr}AAA\" $variable { }\n" =
  { "split_clients"
    { "#string" = "\"${remote_addr}AAA\"" }
    { "#variable" = "$variable" } }

(* upstream block *)
test lns get "upstream backend { }\n" =
 { "upstream"
    { "#name" = "backend" } }

(* GH #179 - recursive blocks *)
let http = "http {
  server {
    listen 80;
    location / {
      root\thtml;
    }
  }
  gzip on;
}\n"

test lns get http =
  { "http"
    { "server"
       { "listen" = "80" }
       { "location"
         { "#uri" = "/" }
         { "root" = "html" } } }
    { "gzip" = "on" } }

(* Make sure we do not screw up the indentation of the file *)
test lns put http after set "/http/gzip" "off" =
"http {
  server {
    listen 80;
    location / {
      root\thtml;
    }
  }
  gzip off;
}\n"