Add a test to check we take the right branch

This does essentially the same as the test Pass_prefix_union introduced
with cset 489:3f4414038a30, but in a real world lens.

module Test_yum =

  let yum_simple = "[sec1]
# comment
key=value
[sec-two]
key1=value1
# comment
key2=value2
"

  let yum_conf = "[main]
cachedir=/var/cache/yum
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
metadata_expire=1800

installonly_limit=100

# PUT YOUR REPOS HERE OR IN separate files named file.repo
# in /etc/yum.repos.d
"

  let cont = "[main]\nbaseurl=url1\n   url2 , url3\n   \n"

  test Yum.lns get yum_simple =
    { "sec1" {} { "key" = "value" } }
    { "sec-two" { "key1" = "value1" } {} { "key2" = "value2" } }

  test Yum.lns put yum_conf after
      rm "main"
    = ""

  test Yum.lns put yum_simple after
      set "sec1/key" "othervalue"
    = "[sec1]\n# comment\nkey=othervalue\n[sec-two]\nkey1=value1\n# comment\nkey2=value2\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      rm "sec-two/key1"
  = "[sec-two]\n# comment\nkey2=value2\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      rm "sec-two/key1" ;
      set "sec-two/newkey" "newvalue"
  = "[sec-two]\n# comment\nkey2=value2\nnewkey=newvalue\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      set "sec-two/key1" "newvalue"
   = "[sec-two]\nkey1=newvalue\n# comment\nkey2=value2\n"

  test Yum.lns get cont =
    { "main"
        { "baseurl" = "url1" }
        { "baseurl" = "url2" }
        { "baseurl" = "url3" }
        {}
    }

  test Yum.lns put cont after
      set "main/gpgcheck" "1"
  =
    cont . "gpgcheck=1\n"

  (* We are actually stricter than yum in checking syntax. The yum.conf *)
  (* man page mentions that it is illegal to have multiple baseurl keys *)
  (* in the same section, but yum will just carry on, usually with      *)
  (* results that surpise the unsuspecting user                         *)
  test Yum.lns get "[repo]\nbaseurl=url1\nbaseurl=url2\n" = *

  (* This checks that we take the right branch in the section lens.     *)
  test Yum.section get "[repo]\nname=A name\nbaseurl=url1\n" =
    { "repo"
        { "name" = "A name" }
        { "baseurl" = "url1" } }

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

module Test_yum =

  let yum_simple = "[sec1]
# comment
key=value
[sec-two]
key1=value1
# comment
key2=value2
"

  let yum_conf = "[main]
cachedir=/var/cache/yum
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
metadata_expire=1800

installonly_limit=100

# PUT YOUR REPOS HERE OR IN separate files named file.repo
# in /etc/yum.repos.d
"

  let cont = "[main]\nbaseurl=url1\n   url2 , url3\n   \n"

  test Yum.lns get yum_simple =
    { "sec1" {} { "key" = "value" } }
    { "sec-two" { "key1" = "value1" } {} { "key2" = "value2" } }

  test Yum.lns put yum_conf after
      rm "main"
    = ""

  test Yum.lns put yum_simple after
      set "sec1/key" "othervalue"
    = "[sec1]\n# comment\nkey=othervalue\n[sec-two]\nkey1=value1\n# comment\nkey2=value2\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      rm "sec-two/key1"
  = "[sec-two]\n# comment\nkey2=value2\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      rm "sec-two/key1" ;
      set "sec-two/newkey" "newvalue"
  = "[sec-two]\n# comment\nkey2=value2\nnewkey=newvalue\n"

  test Yum.lns put yum_simple after
      rm "sec1" ;
      set "sec-two/key1" "newvalue"
   = "[sec-two]\nkey1=newvalue\n# comment\nkey2=value2\n"

  test Yum.lns get cont =
    { "main"
        { "baseurl" = "url1" }
        { "baseurl" = "url2" }
        { "baseurl" = "url3" }
        {}
    }

  test Yum.lns put cont after
      set "main/gpgcheck" "1"
  =
    cont . "gpgcheck=1\n"

  (* We are actually stricter than yum in checking syntax. The yum.conf *)
  (* man page mentions that it is illegal to have multiple baseurl keys *)
  (* in the same section, but yum will just carry on, usually with      *)
  (* results that surpise the unsuspecting user                         *)
  test Yum.lns get "[repo]\nbaseurl=url1\nbaseurl=url2\n" = *

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


yum.aug: Yum lenses work on DNF, add them to import list (#434)


Mdadm_Conf: optimize regexp substractions

(******************************************************************************
Mdadm_conf module for Augeas

Author: Matthew Booth <mbooth@redhat.com>

Copyright (C):
    2011 Red Hat Inc.

Reference:
    mdadm(5)
    config.c and policy.c from mdadm-3.2.2

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

This is a lens for /etc/mdadm.conf. It aims to parse every valid configuration
file as of version 3.2.2, and many invalid ones too. This last point is a
feature, not a bug! madm will generate warnings for invalid configuration which
do not prevent correct operation of the tool. Wherever possible, we try to
allow for this behaviour.

Keywords in mdadm.conf are matched with a case-insensitive prefix match of at
least 3 characters. Keys in key/value pairs are also matched case-insensitively,
but require a full match. The exception is POLICY and PART-POLICY, where keys
are matched case-sensitively.

N.B. We can't use case-insensitive regular expressions in most places due to bug
#147.
*******************************************************************************)

module Mdadm_conf =

   autoload xfm

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

let eol             = Util.comment_or_eol
let comment         = Util.comment
let empty           = Util.empty
let value           = /[^ \t\n#]+/
let value_no_eq     = /[^ \t\n#=]+/
let value_no_eq_sl  = /[^ \t\n#=\/]+/

let continuation = /\n[ \t]+/
let space = /[ \t]+/
let value_sep = ( del ( continuation | space . continuation? ) " "
                  | comment . del space " " )

(* We parse specific keys rather than having a catch-all owing to the varying
case of the syntax. This means the user can rely on 'array/uuid' rather than
additionally testing for 'array/UUID'.

It would be good to have an additional catchall, but I haven't been able to make
that work.
*)
let keyvalue (r:regexp) (lc:string) (uc:string) =
    [ del ( r . /=/ ) ( uc . "=" ) . label lc . store value ]

let simplevalue (r:regexp) (lc:string) (uc:string) =
    [ del r uc . label lc
      . ( value_sep . [ label "value" . store value ] )* . eol ]

(******************************************************************************
 * DEVICES
 ******************************************************************************)

let dev_re = /dev(i(ce?)?)?/i

let dev_containers_re = /containers/i
let dev_partitions_re = /partitions/i

let dev_containers = [ del dev_containers_re "containers" . label "containers" ]
let dev_partitions = [ del dev_partitions_re "partitions" . label "partitions" ]
let dev_device = [ label "name". store ( value - (dev_containers_re | dev_partitions_re)) ]

(* Strictly there must be at least 1 device, but we err on the side of parsing
*)
let dev_devices = ( value_sep . ( dev_containers
                                  | dev_partitions
                                  | dev_device ) )*

let device = [ del dev_re "DEVICE" . label "device" . dev_devices . eol ]

(******************************************************************************
 * ARRAY
 ******************************************************************************)

let array_re  = /arr(ay?)?/i

let arr_auto_re         = /auto/i
let arr_bitmap_re       = /bitmap/i
let arr_container_re    = /container/i
let arr_devices_re      = /devices/i
let arr_disks_re        = /disks/i (* Undocumented *)
let arr_level_re        = /level/i
let arr_member_re       = /member/i
let arr_metadata_re     = /metadata/i
let arr_name_re         = /name/i
let arr_num_devices_re  = /num-devices/i
let arr_spare_group_re  = /spare-group/i
let arr_spares_re       = /spares/i
let arr_super_minor_re  = /super-minor/i
let arr_uuid_re         = /uuid/i

let arr_devicename      = [ store value_no_eq . label "devicename" ]

let arr_auto        = keyvalue arr_auto_re "auto" "AUTO"
let arr_bitmap      = keyvalue arr_bitmap_re "bitmap" "BITMAP"
let arr_container   = keyvalue arr_container_re "container" "CONTAINER"
let arr_devices     = keyvalue arr_devices_re "devices" "DEVICES"
let arr_disks       = keyvalue arr_disks_re "disks" "DISKS"
let arr_level       = keyvalue arr_level_re "level" "LEVEL"
let arr_member      = keyvalue arr_member_re "member" "MEMBER"
let arr_metadata    = keyvalue arr_metadata_re "metadata" "METADATA"
let arr_name        = keyvalue arr_name_re "name" "NAME"
let arr_num_devices = keyvalue arr_num_devices_re "num-devices" "NUM-DEVICES"
let arr_spare_group = keyvalue arr_spare_group_re "spare-group" "SPARE-GROUP"
let arr_spares      = keyvalue arr_spares_re "spares" "SPARES"
let arr_super_minor = keyvalue arr_super_minor_re "super-minor" "SUPER-MINOR"
let arr_uuid        = keyvalue arr_uuid_re "uuid" "UUID"

let arr_options = ( value_sep . ( arr_devicename
                                  | arr_auto
                                  | arr_bitmap
                                  | arr_container
                                  | arr_devices
                                  | arr_disks
                                  | arr_level
                                  | arr_member
                                  | arr_metadata
                                  | arr_name
                                  | arr_num_devices
                                  | arr_spare_group
                                  | arr_spares
                                  | arr_super_minor
                                  | arr_uuid ) )*

let array  = [ del array_re "ARRAY" . label "array" . arr_options . eol ]

(******************************************************************************
 * MAILADDR
 ******************************************************************************)

let mailaddr_re = /mai(l(a(d(dr?)?)?)?)?/i

(* We intentially allow multiple mailaddr values here, even though this is
invalid and would produce a warning. This is better than not parsing the file.
*)
let mailaddr = simplevalue mailaddr_re "mailaddr" "MAILADDR"

(******************************************************************************
 * MAILFROM
 ******************************************************************************)

(* N.B. MAILFROM can only be abbreviated to 5 characters *)
let mailfrom_re = /mailf(r(om?)?)?/i

let mailfrom = [ del mailfrom_re "MAILFROM" . label "mailfrom"
                 . ( value_sep . [ label "value" . store value ] )* . eol ]

(******************************************************************************
 * PROGRAM
 ******************************************************************************)

let program_re = /pro(g(r(am?)?)?)?/i

let program = simplevalue program_re "program" "PROGRAM"

(******************************************************************************
 * CREATE
 ******************************************************************************)

let create_re = /cre(a(te?)?)?/i

let cre_auto_re     = /auto/i
let cre_owner_re    = /owner/i
let cre_group_re    = /group/i
let cre_mode_re     = /mode/i
let cre_metadata_re = /metadata/i
let cre_symlinks_re = /symlinks/i

let cre_auto        = keyvalue cre_auto_re "auto" "AUTO"
let cre_group       = keyvalue cre_group_re "group" "GROUP"
let cre_metadata    = keyvalue cre_metadata_re "metadata" "METADATA"
let cre_mode        = keyvalue cre_mode_re "mode" "MODE"
let cre_owner       = keyvalue cre_owner_re "owner" "OWNER"
let cre_symlinks    = keyvalue cre_symlinks_re "symlinks" "SYMLINKS"

let cre_options = ( value_sep . ( arr_auto
                                  | cre_owner
                                  | cre_group
                                  | cre_mode
                                  | cre_metadata
                                  | cre_symlinks) )*

let create  = [ del create_re "CREATE" . label "create" . cre_options . eol ]

(******************************************************************************
 * HOMEHOST
 ******************************************************************************)

let homehost_re = /hom(e(h(o(st?)?)?)?)?/i

let homehost = simplevalue homehost_re "homehost" "HOMEHOST"

(******************************************************************************
 * AUTO
 ******************************************************************************)

let auto_re = /auto?/i

let aut_plus        = [ key "+" . store value ]
let aut_minus       = [ key "-" . store value ]
let aut_homehost    = [ del /homehost/i "homehost" . label "homehost" ]

let aut_list = ( value_sep . ( aut_plus | aut_minus | aut_homehost ) )*

let auto = [ del auto_re "AUTO" . label "auto" . aut_list . eol ]

(******************************************************************************
 * POLICY and PART-POLICY
 ******************************************************************************)

(* PART-POLICY is undocumented. A cursory inspection of the parsing code
suggests it's parsed the same way as POLICY, but treated slightly differently
thereafter. *)

let policy_re = /pol(i(cy?)?)?/i
let part_policy_re = /par(t(-(p(o(l(i(cy?)?)?)?)?)?)?)?/i

(* Unlike everything else, policy keys are matched case sensitive. This means we
don't have to mess around with explicit option matching, as the match string is
fixed for a working configuration. *)

let pol_option (act:string) =
    [ del ( act . "=" ) ( act . "=" ) . label act . store value ]

let pol_options = ( value_sep . [ key value_no_eq_sl . del "=" "="
                                  . store value ] )*

let policy      = [ del policy_re "POLICY" . label "policy"
                    . pol_options . eol ]
let part_policy = [ del part_policy_re "PART-POLICY" . label "part-policy"
                    . pol_options . eol ]

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

let lns = (comment
           | empty
           | device
           | array
           | mailaddr
           | mailfrom
           | program
           | create
           | homehost
           | auto
           | policy
           | part_policy )*

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

let xfm        = transform lns filter

(******************************************************************************
Mdadm_conf module for Augeas

Author: Matthew Booth <mbooth@redhat.com>

Copyright (C):
    2011 Red Hat Inc.

Reference:
    mdadm(5)
    config.c and policy.c from mdadm-3.2.2

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

This is a lens for /etc/mdadm.conf. It aims to parse every valid configuration
file as of version 3.2.2, and many invalid ones too. This last point is a
feature, not a bug! madm will generate warnings for invalid configuration which
do not prevent correct operation of the tool. Wherever possible, we try to
allow for this behaviour.

Keywords in mdadm.conf are matched with a case-insensitive prefix match of at
least 3 characters. Keys in key/value pairs are also matched case-insensitively,
but require a full match. The exception is POLICY and PART-POLICY, where keys
are matched case-sensitively.

N.B. We can't use case-insensitive regular expressions in most places due to bug
#147.
*******************************************************************************)

module Mdadm_conf =

   autoload xfm

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

let eol             = Util.comment_or_eol
let comment         = Util.comment
let empty           = Util.empty
let value           = /[^ \t\n#]+/
let value_no_eq     = /[^ \t\n#=]+/
let value_no_eq_sl  = /[^ \t\n#=\/]+/

let continuation = /\n[ \t]+/
let space = /[ \t]+/
let value_sep = ( del ( continuation | space . continuation? ) " "
                  | comment . del space " " )

(* We parse specific keys rather than having a catch-all owing to the varying
case of the syntax. This means the user can rely on 'array/uuid' rather than
additionally testing for 'array/UUID'.

It would be good to have an additional catchall, but I haven't been able to make
that work.
*)
let keyvalue (r:regexp) (lc:string) (uc:string) =
    [ del ( r . /=/ ) ( uc . "=" ) . label lc . store value ]

let simplevalue (r:regexp) (lc:string) (uc:string) =
    [ del r uc . label lc
      . ( value_sep . [ label "value" . store value ] )* . eol ]

(******************************************************************************
 * DEVICES
 ******************************************************************************)

let dev_re = /dev(i(ce?)?)?/i

let dev_containers_re = /containers/i
let dev_partitions_re = /partitions/i

let dev_containers = [ del dev_containers_re "containers" . label "containers" ]
let dev_partitions = [ del dev_partitions_re "partitions" . label "partitions" ]
let dev_device = [ label "name". store ( value - dev_containers_re - dev_partitions_re) ]

(* Strictly there must be at least 1 device, but we err on the side of parsing
*)
let dev_devices = ( value_sep . ( dev_containers
                                  | dev_partitions
                                  | dev_device ) )*

let device = [ del dev_re "DEVICE" . label "device" . dev_devices . eol ]

(******************************************************************************
 * ARRAY
 ******************************************************************************)

let array_re  = /arr(ay?)?/i

let arr_auto_re         = /auto/i
let arr_bitmap_re       = /bitmap/i
let arr_container_re    = /container/i
let arr_devices_re      = /devices/i
let arr_disks_re        = /disks/i (* Undocumented *)
let arr_level_re        = /level/i
let arr_member_re       = /member/i
let arr_metadata_re     = /metadata/i
let arr_name_re         = /name/i
let arr_num_devices_re  = /num-devices/i
let arr_spare_group_re  = /spare-group/i
let arr_spares_re       = /spares/i
let arr_super_minor_re  = /super-minor/i
let arr_uuid_re         = /uuid/i

let arr_devicename      = [ store value_no_eq . label "devicename" ]

let arr_auto        = keyvalue arr_auto_re "auto" "AUTO"
let arr_bitmap      = keyvalue arr_bitmap_re "bitmap" "BITMAP"
let arr_container   = keyvalue arr_container_re "container" "CONTAINER"
let arr_devices     = keyvalue arr_devices_re "devices" "DEVICES"
let arr_disks       = keyvalue arr_disks_re "disks" "DISKS"
let arr_level       = keyvalue arr_level_re "level" "LEVEL"
let arr_member      = keyvalue arr_member_re "member" "MEMBER"
let arr_metadata    = keyvalue arr_metadata_re "metadata" "METADATA"
let arr_name        = keyvalue arr_name_re "name" "NAME"
let arr_num_devices = keyvalue arr_num_devices_re "num-devices" "NUM-DEVICES"
let arr_spare_group = keyvalue arr_spare_group_re "spare-group" "SPARE-GROUP"
let arr_spares      = keyvalue arr_spares_re "spares" "SPARES"
let arr_super_minor = keyvalue arr_super_minor_re "super-minor" "SUPER-MINOR"
let arr_uuid        = keyvalue arr_uuid_re "uuid" "UUID"

let arr_options = ( value_sep . ( arr_devicename
                                  | arr_auto
                                  | arr_bitmap
                                  | arr_container
                                  | arr_devices
                                  | arr_disks
                                  | arr_level
                                  | arr_member
                                  | arr_metadata
                                  | arr_name
                                  | arr_num_devices
                                  | arr_spare_group
                                  | arr_spares
                                  | arr_super_minor
                                  | arr_uuid ) )*

let array  = [ del array_re "ARRAY" . label "array" . arr_options . eol ]

(******************************************************************************
 * MAILADDR
 ******************************************************************************)

let mailaddr_re = /mai(l(a(d(dr?)?)?)?)?/i

(* We intentially allow multiple mailaddr values here, even though this is
invalid and would produce a warning. This is better than not parsing the file.
*)
let mailaddr = simplevalue mailaddr_re "mailaddr" "MAILADDR"

(******************************************************************************
 * MAILFROM
 ******************************************************************************)

(* N.B. MAILFROM can only be abbreviated to 5 characters *)
let mailfrom_re = /mailf(r(om?)?)?/i

let mailfrom = [ del mailfrom_re "MAILFROM" . label "mailfrom"
                 . ( value_sep . [ label "value" . store value ] )* . eol ]

(******************************************************************************
 * PROGRAM
 ******************************************************************************)

let program_re = /pro(g(r(am?)?)?)?/i

let program = simplevalue program_re "program" "PROGRAM"

(******************************************************************************
 * CREATE
 ******************************************************************************)

let create_re = /cre(a(te?)?)?/i

let cre_auto_re     = /auto/i
let cre_owner_re    = /owner/i
let cre_group_re    = /group/i
let cre_mode_re     = /mode/i
let cre_metadata_re = /metadata/i
let cre_symlinks_re = /symlinks/i

let cre_auto        = keyvalue cre_auto_re "auto" "AUTO"
let cre_group       = keyvalue cre_group_re "group" "GROUP"
let cre_metadata    = keyvalue cre_metadata_re "metadata" "METADATA"
let cre_mode        = keyvalue cre_mode_re "mode" "MODE"
let cre_owner       = keyvalue cre_owner_re "owner" "OWNER"
let cre_symlinks    = keyvalue cre_symlinks_re "symlinks" "SYMLINKS"

let cre_options = ( value_sep . ( arr_auto
                                  | cre_owner
                                  | cre_group
                                  | cre_mode
                                  | cre_metadata
                                  | cre_symlinks) )*

let create  = [ del create_re "CREATE" . label "create" . cre_options . eol ]

(******************************************************************************
 * HOMEHOST
 ******************************************************************************)

let homehost_re = /hom(e(h(o(st?)?)?)?)?/i

let homehost = simplevalue homehost_re "homehost" "HOMEHOST"

(******************************************************************************
 * AUTO
 ******************************************************************************)

let auto_re = /auto?/i

let aut_plus        = [ key "+" . store value ]
let aut_minus       = [ key "-" . store value ]
let aut_homehost    = [ del /homehost/i "homehost" . label "homehost" ]

let aut_list = ( value_sep . ( aut_plus | aut_minus | aut_homehost ) )*

let auto = [ del auto_re "AUTO" . label "auto" . aut_list . eol ]

(******************************************************************************
 * POLICY and PART-POLICY
 ******************************************************************************)

(* PART-POLICY is undocumented. A cursory inspection of the parsing code
suggests it's parsed the same way as POLICY, but treated slightly differently
thereafter. *)

let policy_re = /pol(i(cy?)?)?/i
let part_policy_re = /par(t(-(p(o(l(i(cy?)?)?)?)?)?)?)?/i

(* Unlike everything else, policy keys are matched case sensitive. This means we
don't have to mess around with explicit option matching, as the match string is
fixed for a working configuration. *)

let pol_option (act:string) =
    [ del ( act . "=" ) ( act . "=" ) . label act . store value ]

let pol_options = ( value_sep . [ key value_no_eq_sl . del "=" "="
                                  . store value ] )*

let policy      = [ del policy_re "POLICY" . label "policy"
                    . pol_options . eol ]
let part_policy = [ del part_policy_re "PART-POLICY" . label "part-policy"
                    . pol_options . eol ]

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

let lns = (comment
           | empty
           | device
           | array
           | mailaddr
           | mailfrom
           | program
           | create
           | homehost
           | auto
           | policy
           | part_policy )*

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

let xfm        = transform lns filter


Shellvars: Optimize regexp substraction

Shellvars: Optimize regexp substraction

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

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

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

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

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

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

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

  let char  = /[^`;() '"\t\n]|\\\\"/
  let dquot = /"([^"\\]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]*`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\n;]+``/
  let dollar_assign = /\$\([^\)#\n]*\)/

  let sto_to_semicol = store /[^#; \t\n][^#;\n]+[^#; \t\n]|[^#; \t\n]+/

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

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

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

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store (key_re | matching_re) . comment_or_eol ]

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

  let source =
    [ Util.indent
      . del /\.|source/ "." . label ".source"
      . Util.del_ws_spc . store /[^;=# \t\n]+/ . comment_or_eol ]

  let shell_builtin_cmds = "ulimit" | "shift" | "exit"

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

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

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

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

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

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

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

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

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

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

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

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry*
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store (char+ | ("\"" . char+ . "\""))
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . (empty* . comment* . case_entry)*
        . empty* . comment*
        . keyword "esac" . comment_or_eol ]

  let function (entry:lens) =
    [ Util.indent . label "@function"
      . del /(function[ \t]+)?/ ""
      . store Rx.word . del /[ \t]*\(\)/ "()"
      . eol . Util.del_str "{" . eol
      . entry+
      . Util.indent . Util.del_str "}" . eol ]

  let rec rec_entry =
    let entry = comment | source | kv
              | unset | bare_export | builtin | return | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry
      | function entry

  let lns_norec = empty* . (comment | source | kv | unset | bare_export | builtin | return) *

  let lns = empty* . (comment | source | kv | unset | bare_export | builtin | return | rec_entry) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let sc_excl (n:string) = (excl ("/etc/sysconfig/" . n))

  let filter_sysconfig =
      sc_incl "*" .
      sc_excl "bootloader" .
      sc_excl "hw-uuid" .
      sc_excl "hwconf" .
      sc_excl "ip*tables" .
      sc_excl "kernel" .
      sc_excl "*.pub" .
      sc_excl "sysstat.ioconf" .
      sc_excl "system-config-firewall" .
      sc_excl "system-config-securitylevel" .
      sc_incl "network/config" .
      sc_incl "network/dhcp" .
      sc_incl "network/dhcp6r" .
      sc_incl "network/dhcp6s" .
      sc_incl "network/ifcfg-*" .
      sc_incl "network/if-down.d/*" .
      sc_incl "network/ifroute-*" .
      sc_incl "network/if-up.d/*" .
      sc_incl "network/providers/*" .
      sc_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      (* NetworkManager used to write files with '!' in them; this has
         been patched upstream. We need to ignore these files, because
         their name causes trouble during aug_init *)
      sc_excl "network-scripts/ifcfg-*!*" .
      (* Ditto, brackets conflict with paths and cause aug_init failures *)
      sc_excl "network-scripts/ifcfg-*(*" .
      sc_excl "network-scripts/ifcfg-*)*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/rc.conf.local"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"
                     . incl "/etc/locale.conf"
                     . incl "/etc/vconsole.conf"

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

  let xfm = transform lns filter

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

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

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

About: Lens Usage
   To be documented
*)

module Shellvars =
  autoload xfm

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

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

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

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

  let char  = /[^`;() '"\t\n]|\\\\"/
  let dquot = /"([^"\\]|\\\\.)*"/                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]*`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\n;]+``/
  let dollar_assign = /\$\([^\)#\n]*\)/

  let sto_to_semicol = store /[^#; \t\n][^#;\n]+[^#; \t\n]|[^#; \t\n]+/

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

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

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

  let var_action (name:string) =
    [ Util.indent . xchgs name ("@" . name) . Util.del_ws_spc
    . store (key_re | matching_re) . comment_or_eol ]

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

  let source =
    [ Util.indent
      . del /\.|source/ "." . label ".source"
      . Util.del_ws_spc . store /[^;=# \t\n]+/ . comment_or_eol ]

  let shell_builtin_cmds = "ulimit" | "shift" | "exit"

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

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

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

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

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

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

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

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

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

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

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

  let case (entry:lens) =
    let case_entry = [ label "@case_entry"
                       . Util.indent . store /[^ \t\n\)]+/
                       . Util.del_str ")" . eol
                       . entry*
                       . Util.indent . Util.del_str ";;" . eol ] in
      [ keyword_label "case" "@case" . Sep.space
        . store (char+ | ("\"" . char+ . "\""))
        . del /[ \t\n]+/ " " . Util.del_str "in" . eol
        . (empty* . comment* . case_entry)*
        . empty* . comment*
        . keyword "esac" . comment_or_eol ]

  let function (entry:lens) =
    [ Util.indent . label "@function"
      . del /(function[ \t]+)?/ ""
      . store Rx.word . del /[ \t]*\(\)/ "()"
      . eol . Util.del_str "{" . eol
      . entry+
      . Util.indent . Util.del_str "}" . eol ]

  let rec rec_entry =
    let entry = comment | source | kv
              | unset | bare_export | builtin | return | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry
      | function entry

  let lns_norec = empty* . (comment | source | kv | unset | bare_export | builtin | return) *

  let lns = empty* . (comment | source | kv | unset | bare_export | builtin | return | rec_entry) *

  let sc_incl (n:string) = (incl ("/etc/sysconfig/" . n))
  let sc_excl (n:string) = (excl ("/etc/sysconfig/" . n))

  let filter_sysconfig =
      sc_incl "*" .
      sc_excl "bootloader" .
      sc_excl "hw-uuid" .
      sc_excl "hwconf" .
      sc_excl "ip*tables" .
      sc_excl "kernel" .
      sc_excl "*.pub" .
      sc_excl "sysstat.ioconf" .
      sc_excl "system-config-firewall" .
      sc_excl "system-config-securitylevel" .
      sc_incl "network/config" .
      sc_incl "network/dhcp" .
      sc_incl "network/dhcp6r" .
      sc_incl "network/dhcp6s" .
      sc_incl "network/ifcfg-*" .
      sc_incl "network/if-down.d/*" .
      sc_incl "network/ifroute-*" .
      sc_incl "network/if-up.d/*" .
      sc_incl "network/providers/*" .
      sc_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      (* NetworkManager used to write files with '!' in them; this has
         been patched upstream. We need to ignore these files, because
         their name causes trouble during aug_init *)
      sc_excl "network-scripts/ifcfg-*!*" .
      (* Ditto, brackets conflict with paths and cause aug_init failures *)
      sc_excl "network-scripts/ifcfg-*(*" .
      sc_excl "network-scripts/ifcfg-*)*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/whoopsie"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/blkid.conf"
                     . incl "/etc/adduser.conf"
                     . incl "/etc/cowpoke.conf"
                     . incl "/etc/cvs-cron.conf"
                     . incl "/etc/cvs-pserver.conf"
                     . incl "/etc/devscripts.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/rc.conf.local"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"
                     . incl "/etc/locale.conf"
                     . incl "/etc/vconsole.conf"

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

  let xfm = transform lns filter

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


Deckard: support RFC 3597 syntax in message definitions

module Deckard =
  autoload xfm

let del_str = Util.del_str

let space = del /[ \t]+/ " "
let tab = del /[ \t]+/ "\t"
let ws = del /[\t ]*/ ""
let word = /[^\t\n\/#; ]+/

let comment = del /[;#]/ ";" . [label "comment" . store /[^\n]+/]

let eol = del /([ \t]*([;#][^\n]*)?\n)+/ "\n" . Util.indent
let comment_or_eol =  ws . comment? . del_str "\n" . del /([ \t]*([;#][^\n]*)?\n)*/ "\n" . Util.indent

(*let comment_or_eol = [ label "#comment" . counter "comment" . (ws . [del /[;#]/ ";" . label "" . store /[^\n]*/ ]? . del_str "\n")]+ . Util.indent
*)

let domain_re = (/[^.\t\n\/#; ]+(\.[^.\t\n\/#; ]+)*\.?/ | ".") - "SECTION" (*quick n dirty, sorry to whoever will ever own SECTION TLD*)
let class_re = /CLASS[0-9]+/ | "IN" | "CH" | "HS" | "NONE" | "ANY"
let domain = [ label "domain" . store domain_re ]
let ttl = [label "ttl" . store /[0-9]+/]
let class = [label "class" . store class_re ]
let type = [label "type" . store ((/[^0-9#;\n \t][^\t\n\/#; ]*/) - class_re) ]
(* RFC 3597 section 5 rdata syntax is "\# 1 ab"*)
let data_re = /((\\\#[ \t])?[^ \t\n#;][^\n#;]*[^ \t\n#;])|[^ \t\n#;]/ (*Can not start nor end with whitespace but can have whitespace in the middle. Disjunction is there so we match strings of length one.*)
let data = [label "data" . store data_re ]

let ip_re = /[0-9a-f.:]+/
let hex_re = /[0-9a-fA-F]+/

let match_option =  "opcode" | "qtype" | "qcase" | "qname" | "subdomain" | "flags" | "rcode" | "question" | "answer" | "authority" | "additional" | "all" | "TCP" | "ttl"
let adjust_option = "copy_id" | "copy_query"
let reply_option = "QR" | "TC" | "AA" | "AD" | "RD" | "RA" | "CD" | "DO" | "NOERROR" | "FORMERR" | "SERVFAIL" | "NXDOMAIN" | "NOTIMP" | "REFUSED" | "YXDOMAIN" | "YXRRSET" | "NXRRSET" | "NOTAUTH" | "NOTZONE" | "BADVERS" | "BADSIG" | "BADKEY" | "BADTIME" | "BADMODE" | "BADNAME" | "BADALG" | "BADTRUNC" | "BADCOOKIE"
let step_option = "REPLY" | "QUERY" | "CHECK_ANSWER" | "CHECK_OUT_QUERY" | /TIME_PASSES[ \t]+ELAPSE/

let mandatory = [del_str "MANDATORY" . label "mandatory" . value "true" . comment_or_eol]
let tsig = [del_str "TSIG" . label "tsig" . space . [label "keyname" . store word] . space . [label "secret" . store word] . comment_or_eol]

let match = (mandatory | tsig)* . del_str "MATCH" . [space . label "match" . store match_option ]+ . comment_or_eol
let adjust =  (mandatory | tsig)* . del_str "ADJUST" . [space . label "adjust" . store adjust_option ]+ . comment_or_eol
let reply =  (mandatory | tsig)* . del ("REPLY" | "FLAGS") "REPLY" .  [space . label "reply" . store reply_option ]+ . comment_or_eol

let question = [label "record" . domain . tab . (class . tab)? . type . comment_or_eol ]
let record = [label "record" . domain . tab . (ttl . tab)? . (class . tab)? . type . tab . data . comment_or_eol]

let section_question = [ label "question" . del_str "SECTION QUESTION" .
                       comment_or_eol . question? ]
let section_answer = [ label "answer" . del_str "SECTION ANSWER" .
                       comment_or_eol . record* ]
let section_authority = [ label "authority" . del_str "SECTION AUTHORITY" .
                          comment_or_eol . record* ]
let section_additional = [ label "additional" . del_str "SECTION ADDITIONAL" .
                           comment_or_eol . record* ]
let sections = [label "section" . section_question? . section_answer? . section_authority? . section_additional?]

let raw = [del_str "RAW" . comment_or_eol . label "raw" . store hex_re  ] . comment_or_eol

(* This is quite dirty hack to match every combination of options given to entry since 'let normal = ((match | adjust | reply | mandatory | tsig)* . sections)' just is not possible *)

let normal = (match . (adjust . reply? | reply . adjust?)? | adjust . (match . reply? | reply . match?)? | reply . (match . adjust? | adjust . match?)?)? . (mandatory | tsig)* . sections

let entry = [label "entry" . del_str "ENTRY_BEGIN" . comment_or_eol . ( normal | raw ) . del_str "ENTRY_END" . eol]

let single_address = [ label "address" . space . store ip_re ]

let addresses = [label "address" . counter "address" . [seq "address" . del_str "ADDRESS" . space . store ip_re . comment_or_eol]+]

let range = [label "range" . del_str "RANGE_BEGIN" . space . [ label "from" . store /[0-9]+/] . space .
            [ label "to" . store /[0-9]+/] . single_address? . comment_or_eol . addresses? . entry* . del_str "RANGE_END" . eol]

let step = [label "step" . del_str "STEP" . space . store /[0-9]+/ . space . [label "type" . store step_option] . [space . label "timestamp" . store /[0-9]+/]? . comment_or_eol .
           entry? ]

let config_record = /[^\n]*/ - ("CONFIG_END" | /STEP.*/ | /SCENARIO.*/ | /RANGE.*/ | /ENTRY.*/)

let config = [ label "config" . counter "config" . [seq "config" . store config_record . del_str "\n"]* . del_str "CONFIG_END" . comment_or_eol ]

let guts = (step | range )*

let scenario = [label "scenario" . del_str "SCENARIO_BEGIN" . space . store data_re . comment_or_eol . guts . del_str "SCENARIO_END" . eol]

let lns = config? . scenario

(* TODO: REPLAY step *)
(* TODO: store all comments into the tree instead of ignoring them *)

(*let filter = incl "/home/test/*.rpl"*)
let filter = incl "/media/test/27159fa1-67d4-4162-8707-cd67900f3b36/stepan/nic/deckard_stable/deckard/sets/resolver/*.rpl"

let xfm = transform lns filter

module Deckard =
  autoload xfm

let del_str = Util.del_str

let space = del /[ \t]+/ " "
let tab = del /[ \t]+/ "\t"
let ws = del /[\t ]*/ ""
let word = /[^\t\n\/#; ]+/

let comment = del /[;#]/ ";" . [label "comment" . store /[^\n]+/]

let eol = del /([ \t]*([;#][^\n]*)?\n)+/ "\n" . Util.indent
let comment_or_eol =  ws . comment? . del_str "\n" . del /([ \t]*([;#][^\n]*)?\n)*/ "\n" . Util.indent

(*let comment_or_eol = [ label "#comment" . counter "comment" . (ws . [del /[;#]/ ";" . label "" . store /[^\n]*/ ]? . del_str "\n")]+ . Util.indent
*)

let domain_re = (/[^.\t\n\/#; ]+(\.[^.\t\n\/#; ]+)*\.?/ | ".") - "SECTION" (*quick n dirty, sorry to whoever will ever own SECTION TLD*)
let class_re = /CLASS[0-9]+/ | "IN" | "CH" | "HS" | "NONE" | "ANY"
let domain = [ label "domain" . store domain_re ]
let ttl = [label "ttl" . store /[0-9]+/]
let class = [label "class" . store class_re ]
let type = [label "type" . store ((/[^0-9#;\n \t][^\t\n\/#; ]*/) - class_re) ]
let data_re = /([^ \t\n#;][^\n#;]*[^ \t\n#;])|[^ \t\n#;]/ (*Can not start nor end with whitespace but can have whitespace in the middle. Disjunction is there so we match strings of length one.*)
let data = [label "data" . store data_re ]

let ip_re = /[0-9a-f.:]+/
let hex_re = /[0-9a-fA-F]+/

let match_option =  "opcode" | "qtype" | "qcase" | "qname" | "subdomain" | "flags" | "rcode" | "question" | "answer" | "authority" | "additional" | "all" | "TCP" | "ttl"
let adjust_option = "copy_id" | "copy_query"
let reply_option = "QR" | "TC" | "AA" | "AD" | "RD" | "RA" | "CD" | "DO" | "NOERROR" | "FORMERR" | "SERVFAIL" | "NXDOMAIN" | "NOTIMP" | "REFUSED" | "YXDOMAIN" | "YXRRSET" | "NXRRSET" | "NOTAUTH" | "NOTZONE" | "BADVERS" | "BADSIG" | "BADKEY" | "BADTIME" | "BADMODE" | "BADNAME" | "BADALG" | "BADTRUNC" | "BADCOOKIE"
let step_option = "REPLY" | "QUERY" | "CHECK_ANSWER" | "CHECK_OUT_QUERY" | /TIME_PASSES[ \t]+ELAPSE/

let mandatory = [del_str "MANDATORY" . label "mandatory" . value "true" . comment_or_eol]
let tsig = [del_str "TSIG" . label "tsig" . space . [label "keyname" . store word] . space . [label "secret" . store word] . comment_or_eol]

let match = (mandatory | tsig)* . del_str "MATCH" . [space . label "match" . store match_option ]+ . comment_or_eol
let adjust =  (mandatory | tsig)* . del_str "ADJUST" . [space . label "adjust" . store adjust_option ]+ . comment_or_eol
let reply =  (mandatory | tsig)* . del ("REPLY" | "FLAGS") "REPLY" .  [space . label "reply" . store reply_option ]+ . comment_or_eol

let question = [label "record" . domain . tab . (class . tab)? . type . comment_or_eol ]
let record = [label "record" . domain . tab . (ttl . tab)? . (class . tab)? . type . tab . data . comment_or_eol]

let section_question = [ label "question" . del_str "SECTION QUESTION" .
                       comment_or_eol . question? ]
let section_answer = [ label "answer" . del_str "SECTION ANSWER" .
                       comment_or_eol . record* ]
let section_authority = [ label "authority" . del_str "SECTION AUTHORITY" .
                          comment_or_eol . record* ]
let section_additional = [ label "additional" . del_str "SECTION ADDITIONAL" .
                           comment_or_eol . record* ]
let sections = [label "section" . section_question? . section_answer? . section_authority? . section_additional?]

let raw = [del_str "RAW" . comment_or_eol . label "raw" . store hex_re  ] . comment_or_eol

(* This is quite dirty hack to match every combination of options given to entry since 'let normal = ((match | adjust | reply | mandatory | tsig)* . sections)' just is not possible *)

let normal = (match . (adjust . reply? | reply . adjust?)? | adjust . (match . reply? | reply . match?)? | reply . (match . adjust? | adjust . match?)?)? . (mandatory | tsig)* . sections

let entry = [label "entry" . del_str "ENTRY_BEGIN" . comment_or_eol . ( normal | raw ) . del_str "ENTRY_END" . eol]

let single_address = [ label "address" . space . store ip_re ]

let addresses = [label "address" . counter "address" . [seq "address" . del_str "ADDRESS" . space . store ip_re . comment_or_eol]+]

let range = [label "range" . del_str "RANGE_BEGIN" . space . [ label "from" . store /[0-9]+/] . space .
            [ label "to" . store /[0-9]+/] . single_address? . comment_or_eol . addresses? . entry* . del_str "RANGE_END" . eol]

let step = [label "step" . del_str "STEP" . space . store /[0-9]+/ . space . [label "type" . store step_option] . [space . label "timestamp" . store /[0-9]+/]? . comment_or_eol .
           entry? ]

let config_record = /[^\n]*/ - ("CONFIG_END" | /STEP.*/ | /SCENARIO.*/ | /RANGE.*/ | /ENTRY.*/)

let config = [ label "config" . counter "config" . [seq "config" . store config_record . del_str "\n"]* . del_str "CONFIG_END" . comment_or_eol ]

let guts = (step | range )*

let scenario = [label "scenario" . del_str "SCENARIO_BEGIN" . space . store data_re . comment_or_eol . guts . del_str "SCENARIO_END" . eol]

let lns = config? . scenario

(* TODO: REPLAY step *)
(* TODO: store all comments into the tree instead of ignoring them *)

(*let filter = incl "/home/test/*.rpl"*)
let filter = incl "/media/test/27159fa1-67d4-4162-8707-cd67900f3b36/stepan/nic/deckard_stable/deckard/sets/resolver/*.rpl"

let xfm = transform lns filter


Multipath: fix syntax error in test

Multipath: fix syntax error in test

Error was introduced in commit ade45812

module Test_multipath =

  let conf = "# Blacklist all devices by default.
blacklist {
        devnode \"*\"
        wwid    *
}

# By default, devices with vendor = \"IBM\" and product = \"S/390.*\" are
# blacklisted. To enable mulitpathing on these devies, uncomment the
# following lines.
blacklist_exceptions {
	device {
		vendor	\"IBM\"
		product	\"S/390.*\"
	}
}

#
# Here is an example of how to configure some standard options.
#

defaults {
	udev_dir		/dev
	polling_interval 	10
	selector		\"round-robin 0\"
	path_grouping_policy	multibus
	getuid_callout		\"/sbin/scsi_id --whitelisted /dev/%n\"
	prio			alua
	path_checker		readsector0
	rr_min_io		100
	max_fds			8192
	rr_weight		priorities
	failback		immediate
	no_path_retry		fail
	user_friendly_names	yes
  dev_loss_tmo  30
  max_polling_interval  300
  verbosity 2
  reassign_maps yes
  fast_io_fail_tmo  5
  async_timeout 5
  flush_on_last_del no
  delay_watch_checks no
  delay_wait_checks no
  find_multipaths yes
  checker_timeout 10
  hwtable_regex_match yes
  reload_readwrite no
  force_sync yes
  config_dir /etc/multipath/conf.d
}

# Sections without empty lines in between
blacklist {
       wwid 26353900f02796769
	devnode \"^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*\"

    # Comments and blank lines inside a section
	devnode \"^hd[a-z]\"

}
multipaths {
	multipath {
		wwid			3600508b4000156d700012000000b0000
		alias			yellow
		path_grouping_policy	multibus
		path_checker		readsector0
		path_selector		\"round-robin 0\"
		failback		manual
		rr_weight		priorities
		no_path_retry		5
        flush_on_last_del yes
	}
	multipath {
		wwid			1DEC_____321816758474
		alias			red
	}
}
devices {
	device {
		vendor			\"COMPAQ  \"
		product			\"HSV110 (C)COMPAQ\"
		path_grouping_policy	multibus
		getuid_callout          \"/sbin/scsi_id --whitelisted /dev/%n\"
		path_checker		readsector0
		path_selector		\"round-robin 0\"
		hardware_handler	\"0\"
		failback		15
		rr_weight		priorities
		rr_min_io_rq		75
		no_path_retry		queue
        reservation_key a12345
	}
	device {
		vendor			\"COMPAQ  \"
		product			\"MSA1000         \"
		path_grouping_policy	multibus
		polling_interval	9
        delay_watch_checks 10
        delay_wait_checks 10
	}
}\n"

test Multipath.lns get conf =
  { "#comment" = "Blacklist all devices by default." }
  { "blacklist"
    { "devnode" = "*" }
    { "wwid"    = "*" } }
  { }
  { "#comment" = "By default, devices with vendor = \"IBM\" and product = \"S/390.*\" are" }
  { "#comment" = "blacklisted. To enable mulitpathing on these devies, uncomment the" }
  { "#comment" = "following lines." }
  { "blacklist_exceptions"
    { "device"
      { "vendor" = "IBM" }
      { "product" = "S/390.*" } } }
  { }
  { }
  { "#comment" = "Here is an example of how to configure some standard options." }
  { }
  { }
  { "defaults"
    { "udev_dir" = "/dev" }
    { "polling_interval" = "10" }
    { "selector" = "round-robin 0" }
    { "path_grouping_policy" = "multibus" }
    { "getuid_callout" = "/sbin/scsi_id --whitelisted /dev/%n" }
    { "prio" = "alua" }
    { "path_checker" = "readsector0" }
    { "rr_min_io" = "100" }
    { "max_fds" = "8192" }
    { "rr_weight" = "priorities" }
    { "failback" = "immediate" }
    { "no_path_retry" = "fail" }
    { "user_friendly_names" = "yes" }
    { "dev_loss_tmo" = "30" }
    { "max_polling_interval" = "300" }
    { "verbosity" = "2" }
    { "reassign_maps" = "yes" }
    { "fast_io_fail_tmo" = "5" }
    { "async_timeout" = "5" }
    { "flush_on_last_del" = "no" }
    { "delay_watch_checks" = "no" }
    { "delay_wait_checks" = "no" }
    { "find_multipaths" = "yes" }
    { "checker_timeout" = "10" }
    { "hwtable_regex_match" = "yes" }
    { "reload_readwrite" = "no" }
    { "force_sync" = "yes" }
    { "config_dir" = "/etc/multipath/conf.d" } }
  { }
  { "#comment" = "Sections without empty lines in between" }
  { "blacklist"
    { "wwid" = "26353900f02796769" }
    { "devnode" = "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" }
    { }
    { "#comment" = "Comments and blank lines inside a section" }
    { "devnode" = "^hd[a-z]" }
    { } }
  { "multipaths"
    { "multipath"
      { "wwid" = "3600508b4000156d700012000000b0000" }
      { "alias" = "yellow" }
      { "path_grouping_policy" = "multibus" }
      { "path_checker" = "readsector0" }
      { "path_selector" = "round-robin 0" }
      { "failback" = "manual" }
      { "rr_weight" = "priorities" }
      { "no_path_retry" = "5" }
      { "flush_on_last_del" = "yes" } }
    { "multipath"
      { "wwid" = "1DEC_____321816758474" }
      { "alias" = "red" } } }
  { "devices"
    { "device"
      { "vendor" = "COMPAQ  " }
      { "product" = "HSV110 (C)COMPAQ" }
      { "path_grouping_policy" = "multibus" }
      { "getuid_callout" = "/sbin/scsi_id --whitelisted /dev/%n" }
      { "path_checker" = "readsector0" }
      { "path_selector" = "round-robin 0" }
      { "hardware_handler" = "0" }
      { "failback" = "15" }
      { "rr_weight" = "priorities" }
      { "rr_min_io_rq" = "75" }
      { "no_path_retry" = "queue" }
      { "reservation_key" = "a12345" } }
    { "device"
      { "vendor" = "COMPAQ  " }
      { "product" = "MSA1000         " }
      { "path_grouping_policy" = "multibus" }
      { "polling_interval" = "9" }
      { "delay_watch_checks" = "10" }
      { "delay_wait_checks" = "10" } } }

test Multipath.lns get "blacklist {
        devnode \".*\"
        wwid \"([a-z]+|ab?c).*\"
}\n" =
  { "blacklist"
    { "devnode" = ".*" }
    { "wwid" = "([a-z]+|ab?c).*" } }

test Multipath.lns get "blacklist {\n property \"[a-z]+\"\n}\n" =
  { "blacklist"
    { "property" = "[a-z]+" } }

(* Check that '""' inside a string works *)
test Multipath.lns get "blacklist {
  device {
    vendor SomeCorp
    product \"2.5\"\" SSD\"
  }
}\n" =
  { "blacklist"
    { "device"
      { "vendor" = "SomeCorp" }
      { "product" = "2.5\"\" SSD" } } }

module Test_multipath =

  let conf = "# Blacklist all devices by default.
blacklist {
        devnode \"*\"
        wwid    *
}

# By default, devices with vendor = \"IBM\" and product = \"S/390.*\" are
# blacklisted. To enable mulitpathing on these devies, uncomment the
# following lines.
blacklist_exceptions {
	device {
		vendor	\"IBM\"
		product	\"S/390.*\"
	}
}

#
# Here is an example of how to configure some standard options.
#

defaults {
	udev_dir		/dev
	polling_interval 	10
	selector		\"round-robin 0\"
	path_grouping_policy	multibus
	getuid_callout		\"/sbin/scsi_id --whitelisted /dev/%n\"
	prio			alua
	path_checker		readsector0
	rr_min_io		100
	max_fds			8192
	rr_weight		priorities
	failback		immediate
	no_path_retry		fail
	user_friendly_names	yes
  dev_loss_tmo  30
  max_polling_interval  300
  verbosity 2
  reassign_maps yes
  fast_io_fail_tmo  5
  async_timeout 5
  flush_on_last_del no
  delay_watch_checks no
  delay_wait_checks no
  find_multipaths yes
  checker_timeout 10
  hwtable_regex_match yes
  reload_readwrite no
  force_sync yes
  config_dir /etc/multipath/conf.d
}

# Sections without empty lines in between
blacklist {
       wwid 26353900f02796769
	devnode \"^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*\"

    # Comments and blank lines inside a section
	devnode \"^hd[a-z]\"

}
multipaths {
	multipath {
		wwid			3600508b4000156d700012000000b0000
		alias			yellow
		path_grouping_policy	multibus
		path_checker		readsector0
		path_selector		\"round-robin 0\"
		failback		manual
		rr_weight		priorities
		no_path_retry		5
        flush_on_last_del yes
	}
	multipath {
		wwid			1DEC_____321816758474
		alias			red
	}
}
devices {
	device {
		vendor			\"COMPAQ  \"
		product			\"HSV110 (C)COMPAQ\"
		path_grouping_policy	multibus
		getuid_callout          \"/sbin/scsi_id --whitelisted /dev/%n\"
		path_checker		readsector0
		path_selector		\"round-robin 0\"
		hardware_handler	\"0\"
		failback		15
		rr_weight		priorities
		rr_min_io_rq		75
		no_path_retry		queue
        reservation_key a12345
	}
	device {
		vendor			\"COMPAQ  \"
		product			\"MSA1000         \"
		path_grouping_policy	multibus
		polling_interval	9
        delay_watch_checks 10
        delay_wait_checks 10
	}
}\n"

test Multipath.lns get conf =
  { "#comment" = "Blacklist all devices by default." }
  { "blacklist"
    { "devnode" = "*" }
    { "wwid"    = "*" } }
  { }
  { "#comment" = "By default, devices with vendor = \"IBM\" and product = \"S/390.*\" are" }
  { "#comment" = "blacklisted. To enable mulitpathing on these devies, uncomment the" }
  { "#comment" = "following lines." }
  { "blacklist_exceptions"
    { "device"
      { "vendor" = "IBM" }
      { "product" = "S/390.*" } } }
  { }
  { }
  { "#comment" = "Here is an example of how to configure some standard options." }
  { }
  { }
  { "defaults"
    { "udev_dir" = "/dev" }
    { "polling_interval" = "10" }
    { "selector" = "round-robin 0" }
    { "path_grouping_policy" = "multibus" }
    { "getuid_callout" = "/sbin/scsi_id --whitelisted /dev/%n" }
    { "prio" = "alua" }
    { "path_checker" = "readsector0" }
    { "rr_min_io" = "100" }
    { "max_fds" = "8192" }
    { "rr_weight" = "priorities" }
    { "failback" = "immediate" }
    { "no_path_retry" = "fail" }
    { "user_friendly_names" = "yes" }
    { "dev_loss_tmo" = "30" }
    { "max_polling_interval" = "300" }
    { "verbosity" = "2" }
    { "reassign_maps" = "yes" }
    { "fast_io_fail_tmo" = "5" }
    { "async_timeout" = "5" }
    { "flush_on_last_del" = "no" }
    { "delay_watch_checks" = "no" }
    { "delay_wait_checks" = "no" }
    { "find_multipaths" = "yes" }
    { "checker_timeout" = "10" }
    { "hwtable_regex_match" = "yes" }
    { "reload_readwrite" = "no" }
    { "force_sync" = "yes" }
    { "config_dir" = "/etc/multipath/conf.d" } }
  { }
  { "#comment" = "Sections without empty lines in between" }
  { "blacklist"
    { "wwid" = "26353900f02796769" }
    { "devnode" = "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" }
    { }
    { "#comment" = "Comments and blank lines inside a section" }
    { "devnode" = "^hd[a-z]" }
    { } }
  { "multipaths"
    { "multipath"
      { "wwid" = "3600508b4000156d700012000000b0000" }
      { "alias" = "yellow" }
      { "path_grouping_policy" = "multibus" }
      { "path_checker" = "readsector0" }
      { "path_selector" = "round-robin 0" }
      { "failback" = "manual" }
      { "rr_weight" = "priorities" }
      { "no_path_retry" = "5" }
      { "flush_on_last_del" = "yes" } }
    { "multipath"
      { "wwid" = "1DEC_____321816758474" }
      { "alias" = "red" } } }
  { "devices"
    { "device"
      { "vendor" = "COMPAQ  " }
      { "product" = "HSV110 (C)COMPAQ" }
      { "path_grouping_policy" = "multibus" }
      { "getuid_callout" = "/sbin/scsi_id --whitelisted /dev/%n" }
      { "path_checker" = "readsector0" }
      { "path_selector" = "round-robin 0" }
      { "hardware_handler" = "0" }
      { "failback" = "15" }
      { "rr_weight" = "priorities" }
      { "rr_min_io_rq" = "75" }
      { "no_path_retry" = "queue" }
      { "reservation_key" = "a12345" } }
    { "device"
      { "vendor" = "COMPAQ  " }
      { "product" = "MSA1000         " }
      { "path_grouping_policy" = "multibus" }
      { "polling_interval" = "9" }
      { "delay_watch_checks" = "10" }
      { "delay_wait_checks" = "10" } } }

test Multipath.lns get "blacklist {
        devnode \".*\"
        wwid \"([a-z]+|ab?c).*\"
}\n" =
  { "blacklist"
    { "devnode" = ".*" }
    { "wwid" = "([a-z]+|ab?c).*" } }

test Multipath.lns get "blacklist {\n property \"[a-z]+\"\n}\n" =
  { "blacklist"
    { "property" = "[a-z]+" } }

(* Check that '""' inside a string works *)
test Multipath.lns get "blacklist {
  device {
    vendor SomeCorp
    product \"2.5\"\" SSD\"
  }
}\n" =
  { "blacklist"
    { "device"
      { "vendor" = "SomeCorp" }
      { "product" = "2.5"" SSD" } } }


util.aug: exclude unneeded files on debian systems (fix ticket #153)

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

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

module Util =

(*
Variable: del_str
  Delete a string and default to it

  Parameters:
     s:string - the string to delete and default to
*)
  let del_str (s:string) = del s s

(*
Variable: del_ws
  Delete mandatory whitespace
*)
  let del_ws = del /[ \t]+/

(*
Variable: del_ws_spc
  Delete mandatory whitespace, default to single space
*)
  let del_ws_spc = del_ws " "

(*
Variable: del_ws_tab
  Delete mandatory whitespace, default to single tab
*)
  let del_ws_tab = del_ws "\t"

(*
Variable: del_opt_ws
  Delete optional whitespace
*)
  let del_opt_ws = del /[ \t]*/

(*
Variable: eol
  Delete end of line, including optional trailing whitespace
*)
  let eol = del /[ \t]*\n/ "\n"

(*
Variable: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment and empty
     This is a general definition of comment and empty.
     It allows indentation for comments, removes the leading and trailing spaces
     of comments and stores them in nodes, except for empty comments which are
     ignored together with empty lines

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

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

(* View: Split *)
(* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
(* appears in a separate subtree. The labels for the subtrees are      *)
(* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

(* Group: Exclusions

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

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

(*
Module: Util
  Generic module providing useful primitives

Author: David Lutterkort

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

module Util =

(*
Variable: del_str
  Delete a string and default to it

  Parameters:
     s:string - the string to delete and default to
*)
  let del_str (s:string) = del s s

(*
Variable: del_ws
  Delete mandatory whitespace
*)
  let del_ws = del /[ \t]+/

(*
Variable: del_ws_spc
  Delete mandatory whitespace, default to single space
*)
  let del_ws_spc = del_ws " "

(*
Variable: del_ws_tab
  Delete mandatory whitespace, default to single tab
*)
  let del_ws_tab = del_ws "\t"

(*
Variable: del_opt_ws
  Delete optional whitespace
*)
  let del_opt_ws = del /[ \t]*/

(*
Variable: eol
  Delete end of line, including optional trailing whitespace
*)
  let eol = del /[ \t]*\n/ "\n"

(*
Variable: indent
  Delete indentation, including leading whitespace
*)
  let indent = del /[ \t]*/ ""

(* Group: Comment and empty
     This is a general definition of comment and empty.
     It allows indentation for comments, removes the leading and trailing spaces
     of comments and stores them in nodes, except for empty comments which are
     ignored together with empty lines

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

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

(* View: Split *)
(* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
(* appears in a separate subtree. The labels for the subtrees are      *)
(* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

(* Group: Exclusions

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

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


include only *.repo files from yum.repos.d;


Fix nocommand as nocomma in test_aliases.aug

(*
Module: Test_Aliases
  Provides unit tests and examples for the <Aliases> lens.
*)

module Test_aliases =

(* Variable: file
   A full configuration file *)
  let file = "#
#  Aliases in this file will NOT be expanded in the header from
#  Mail, but WILL be visible over networks or from /bin/mail.

# Basic system aliases -- these MUST be present.
mailer-daemon:	postmaster
postmaster:	root

# General redirections for pseudo accounts.
bin:		root , adm,
  bob
daemon:		root
adm:		root
file:		/var/foo
pipe1:		|/bin/ls
pipe2 :		|\"/usr/bin/ls args,\"
"

(* Test: Aliases.lns
   Testing <Aliases.lns> on <file> *)
  test Aliases.lns get file =
    { }
    { "#comment" = "Aliases in this file will NOT be expanded in the header from" }
    { "#comment" = "Mail, but WILL be visible over networks or from /bin/mail." }
    {}
    { "#comment" = "Basic system aliases -- these MUST be present." }
    { "1" { "name" = "mailer-daemon" }
          { "value" = "postmaster" } }
    { "2" { "name" = "postmaster" }
          { "value" = "root" } }
    {}
    { "#comment" = "General redirections for pseudo accounts." }
    { "3" { "name" = "bin" }
          { "value" = "root" }
          { "value" = "adm" }
          { "value" = "bob" } }
    { "4" { "name" = "daemon" }
          { "value" = "root" } }
    { "5" { "name" = "adm" }
          { "value" = "root" } }
    { "6" { "name" = "file" }
          { "value" = "/var/foo" } }
    { "7" { "name" = "pipe1" }
          { "value" = "|/bin/ls" } }
    { "8" { "name" = "pipe2" }
          { "value" = "|\"/usr/bin/ls args,\"" } }

(* Test: Aliases.lns
   Put test for <Aliases.lns> on <file> *)
  test Aliases.lns put file after
    rm "/4" ; rm "/5" ; rm "/6" ; rm "/7" ; rm "/8" ;
      set "/1/value[2]" "barbar" ;
      set "/3/value[2]" "ruth"
    = "#
#  Aliases in this file will NOT be expanded in the header from
#  Mail, but WILL be visible over networks or from /bin/mail.

# Basic system aliases -- these MUST be present.
mailer-daemon:	postmaster, barbar
postmaster:	root

# General redirections for pseudo accounts.
bin:		root , ruth,
  bob
"

  (* Test: Aliases.lns
     Schema violation, no 3/name *)
  test Aliases.lns put file after
      rm "/3" ;
      set "/3/value/2" "ruth"
    = *

  (* Variable: nocomma
     Don't have to have whitespace after a comma *)
  let nocomma = "alias: target1,target2\n"

  (* Test: Aliases.lns
     Testing <Aliases.lns> on <nocomma> *)
  test Aliases.lns get nocomma =
    { "1"
        { "name" = "alias" }
        { "value" = "target1" }
        { "value" = "target2" } }

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

(*
Module: Test_Aliases
  Provides unit tests and examples for the <Aliases> lens.
*)

module Test_aliases =

(* Variable: file
   A full configuration file *)
  let file = "#
#  Aliases in this file will NOT be expanded in the header from
#  Mail, but WILL be visible over networks or from /bin/mail.

# Basic system aliases -- these MUST be present.
mailer-daemon:	postmaster
postmaster:	root

# General redirections for pseudo accounts.
bin:		root , adm,
  bob
daemon:		root
adm:		root
file:		/var/foo
pipe1:		|/bin/ls
pipe2 :		|\"/usr/bin/ls args,\"
"

(* Test: Aliases.lns
   Testing <Aliases.lns> on <file> *)
  test Aliases.lns get file =
    { }
    { "#comment" = "Aliases in this file will NOT be expanded in the header from" }
    { "#comment" = "Mail, but WILL be visible over networks or from /bin/mail." }
    {}
    { "#comment" = "Basic system aliases -- these MUST be present." }
    { "1" { "name" = "mailer-daemon" }
          { "value" = "postmaster" } }
    { "2" { "name" = "postmaster" }
          { "value" = "root" } }
    {}
    { "#comment" = "General redirections for pseudo accounts." }
    { "3" { "name" = "bin" }
          { "value" = "root" }
          { "value" = "adm" }
          { "value" = "bob" } }
    { "4" { "name" = "daemon" }
          { "value" = "root" } }
    { "5" { "name" = "adm" }
          { "value" = "root" } }
    { "6" { "name" = "file" }
          { "value" = "/var/foo" } }
    { "7" { "name" = "pipe1" }
          { "value" = "|/bin/ls" } }
    { "8" { "name" = "pipe2" }
          { "value" = "|\"/usr/bin/ls args,\"" } }

(* Test: Aliases.lns
   Put test for <Aliases.lns> on <file> *)
  test Aliases.lns put file after
    rm "/4" ; rm "/5" ; rm "/6" ; rm "/7" ; rm "/8" ;
      set "/1/value[2]" "barbar" ;
      set "/3/value[2]" "ruth"
    = "#
#  Aliases in this file will NOT be expanded in the header from
#  Mail, but WILL be visible over networks or from /bin/mail.

# Basic system aliases -- these MUST be present.
mailer-daemon:	postmaster, barbar
postmaster:	root

# General redirections for pseudo accounts.
bin:		root , ruth,
  bob
"

  (* Test: Aliases.lns
     Schema violation, no 3/name *)
  test Aliases.lns put file after
      rm "/3" ;
      set "/3/value/2" "ruth"
    = *

  (* Variable: nocommand
     Don't have to have whitespace after a comma *)
  let nocomma = "alias: target1,target2\n"

  (* Test: Aliases.lns
     Testing <Aliases.lns> on <nocomma> *)
  test Aliases.lns get nocomma =
    { "1"
        { "name" = "alias" }
        { "value" = "target1" }
        { "value" = "target2" } }

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


put on next line for readability

put on next line for readability

(*
Module: Trapperkeeper
  Parses Trapperkeeper configuration files

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

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to Trapperkeeper webservice configuration files. See <filter>.

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

autoload xfm

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

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

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

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

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

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

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

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

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

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

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

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

     let opt_dquot = del /"?/ ""

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

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

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

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

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

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

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

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

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

let xfm = transform lns filter


Hosts: Refactor module

Hosts: Refactor module

(* Parsing /etc/hosts *)

module Hosts =
  autoload xfm

  let word = /[^# \n\t]+/
  let record = [ seq "host" . Util.indent .
                              [ label "ipaddr" . store  word ] . Sep.tab .
                              [ label "canonical" . store word ] .
                              [ label "alias" . Sep.space . store word ]*
                 . Util.comment_or_eol ]

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

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

(* Parsing /etc/hosts *)

module Hosts =
  autoload xfm

  let sep_tab = Util.del_ws_tab
  let sep_spc = Util.del_ws_spc

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

  let comment = Util.comment
  let comment_or_eol = Util.comment_or_eol
  let empty   = [ del /[ \t]*#?[ \t]*\n/ "\n" ]

  let word = /[^# \n\t]+/
  let record = [ seq "host" . indent .
                              [ label "ipaddr" . store  word ] . sep_tab .
                              [ label "canonical" . store word ] .
                              [ label "alias" . sep_spc . store word ]*
                 . comment_or_eol ]

  let lns = ( empty | comment | record ) *

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


Shellvars: process two more files on Debian

Add /etc/arno-iptables-firewall/debconf.cfg and /etc/cron-apt/config

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

  let eol = Util.eol

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/
  let eq = Util.del_str "="
  let comment = [ del /(#.*)?[ \t]*\n/ "# \n" ]

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

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

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

  let kv = [ key key_re . eq . (simple_value | array) . eol ]

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

  let lns = (comment | source | kv) *

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

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

  let xfm = transform lns filter

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

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

  let eol = Util.eol

  let key_re = /[A-Za-z0-9_]+(\[[0-9]+\])?/
  let eq = Util.del_str "="
  let comment = [ del /(#.*)?[ \t]*\n/ "# \n" ]

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

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

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

  let kv = [ key key_re . eq . (simple_value | array) . eol ]

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

  let lns = (comment | source | kv) *

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

  let filter_ifcfg   = incl "/etc/sysconfig/network-scripts/ifcfg-*"
  let filter_default = incl "/etc/default/*"
  let filter = filter_sysconfig
             . filter_ifcfg
             . filter_default
             . Util.stdexcl

  let xfm = transform lns filter

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


Dovecot: no functional change; remove trailing whitespace

(*
Module: Dovecot
  Parses dovecot configuration files.

Author: Serge Smetana <serge.smetana@gmail.com>
  Acunote http://www.acunote.com
  Pluron, Inc. http://pluron.com

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

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

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

About: TODO
  Support for multiline values like queries in dict-sql.conf
*)

module Dovecot =

  autoload xfm

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

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

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

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

(* View: comment
Map comments in "#comment" nodes. *)
let comment = Util.comment

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

(* Variable: any *)
let any = Rx.no_spaces

(* Variable: value
Match any value after " =".
Should not start and end with spaces. May contain spaces inside *)
let value = any . (Rx.space . any)*

(* View: command_start *)
let command_start = Util.del_str "!"

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

(* Variable: commands *)
let commands = /include|include_try/

(* Variable: block_names *)
let block_names = /dict|userdb|passdb|protocol|service|plugin|namespace|map|fields|unix_listener|fifo_listener|inet_listener/

(* Variable: keys
Match any possible key except commands and block names. *)
let keys = Rx.word - (commands | block_names)

(* View: entry
Map simple "key = value" entries including "key =" entries with empty value. *)
let entry = [ indent . key keys. eq . (Sep.opt_space . store value)? . eol ]

(* View: command
Map commands started with "!". *)
let command = [ command_start . key commands . Sep.space . store Rx.fspath . eol ]

(*
View: dquote_spaces
  Make double quotes mandatory if value contains spaces,
  and optional if value doesn't contain spaces.

Based off Quote.dquote_spaces

Parameters:
  lns1:lens - the lens before
  lns2:lens - the lens after
*)
let dquote_spaces (lns1:lens) (lns2:lens) =
     (* bare has no spaces, and is optionally quoted *)
     let bare = Quote.do_dquote_opt (store /[^" \t\n]+/)
     (* quoted has at least one space, and must be quoted *)
  in let quoted = Quote.do_dquote (store /[^"\n]*[ \t]+[^"\n]*/)
  in [ lns1 . bare . lns2 ] | [ lns1 . quoted . lns2 ]

let mailbox = indent
            . dquote_spaces
               (key /mailbox/ . Sep.space)
               (Build.block_newlines (entry) comment . eol)

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

let block_newlines (entry:lens) (comment:lens) =
      let indent = del Rx.opt_space "\t"
   in del block_ldelim_newlines_re Build.block_ldelim_default
 . ((entry | comment) . (Util.empty | entry | comment)*)?
 . del Build.block_rdelim_newlines_re Build.block_rdelim_newlines_default

(* View: block
Map block enclosed in brackets recursively.
Block may be indented and have optional argument.
Block body may have entries, comments, empty lines, and nested blocks recursively. *)
let rec block = [ indent . key block_names . (Sep.space . Quote.do_dquote_opt (store /[\/A-Za-z0-9_-]+/))? . block_newlines (entry|block|mailbox) comment . eol ]

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

(* View: lns
The Dovecot lens *)
let lns = (comment|empty|entry|command|block)*

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

let xfm = transform lns filter


Properly document this lens.

Properly document this lens.


* lenses/group.aug (password): remove backslashes

Backslashes in char classes are taken literally, they do not escape a
character.


Sep: add semicolon

Sep: add semicolon

(*
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: semicolon *)
let semicolon = Util.del_str ";"

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

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

(* Variable: space_equal *)
let space_equal = Util.delim "="

(* 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"

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

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

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

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

(*
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_equal *)
let space_equal = Util.delim "="

(* 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"

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

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

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

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


Use generic modules in bbhosts.aug

Use generic modules in bbhosts.aug

(* BB-hosts module for Augeas                 *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(*                                            *)
(* Supported :                                *)
(*                                            *)
(* Todo :                                     *)
(*                                            *)

module BBhosts =
    autoload xfm

    (* Define useful shortcuts *)

    let eol = Util.eol
    let eol_no_spc = Util.del_str "\n"
    let sep_spc = Sep.space
    let word  = store /[^|;# \n\t]+/
    let value_to_eol = store /[^ \t][^\n]+/
    let ip    = store Rx.ipv4
    let url   = store /https?:[^;,# \n\t]+/

    (* Define comments and empty lines *)
    let comment = Util.comment
    let empty   = Util.empty

    (* Define host *)
    let host_ip   = [ label "ip" . ip ]
    let host_fqdn = [ label "fqdn" . sep_spc . word ]

    let host_test_url  = [ label "url" . sep_spc . url ]
    let host_test_cont (kw:string) = [ sep_spc . store /!?/ . key kw .
                             (Util.del_str ";" .
                                [ label "url" . word ] .
			        (Util.del_str ";" . [ label "keyword" . word ])?
			     )?
			     ]

    let host_test_flag (kw:string) = [ sep_spc . store /!?/ . key kw ]

    let host_test = host_test_cont "cont"
                  | host_test_cont "contInsecure"
                  | host_test_cont "dns"
		  | host_test_flag "noping"
		  | host_test_flag "noconn"
		  | host_test_flag "ssh"
		  | host_test_flag "smtp"
		  | host_test_flag "pop3"
		  | host_test_flag "imap2"
		  | host_test_flag "telnet"
		  | host_test_flag "BBDISPLAY"
		  | host_test_flag "BBNET"
		  | host_test_flag "BBPAGER"
                  | host_test_url

    let host_opts = [ label "probes" . sep_spc . Util.del_str "#" . host_test* ]

    let host = [ label "host" . host_ip . host_fqdn . host_opts . eol ]

    (* Define group-compress and group-only *)
    let group_compress = [ key "group-compress" . sep_spc . value_to_eol . eol_no_spc .
                  ( comment | empty | host)*
		  ]

    let group_only_col  = [ label "col" . word ]
    let group_only_cols = sep_spc . group_only_col . ( Util.del_str "|" . group_only_col )*
    let group_only      = [ key "group-only" . group_only_cols . sep_spc . value_to_eol . eol_no_spc .
                  ( comment | empty | host)*
		  ]

    (* Define page *)
    let page_title = [ label "title" . sep_spc . value_to_eol . eol_no_spc ]
    let page = [ key "page" . sep_spc . word . page_title .
                  ( comment | empty | host )* . ( group_compress | group_only )*
		  ]

    (* Define lens *)

    let lns = (comment | empty)* . page*

    let filter = incl "/etc/bb/bb-hosts"
               . Util.stdexcl

    let xfm = transform lns filter

(* BB-hosts module for Augeas                 *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(*                                            *)
(* Supported :                                *)
(*                                            *)
(* Todo :                                     *)
(*                                            *)

module BBhosts =
    autoload xfm

    (* Define useful shortcuts *)

    let eol = del /[ \t]*\n/ "\n"
    let eol_no_spc = Util.del_str "\n"
    let sep_spc = Util.del_ws_spc
    let word  = store /[^|;# \n\t]+/
    let value_to_eol = store /[^ \t][^\n]+/
    let ip    = store /[0-9\.]+/
    let url   = store /https?:[^;,# \n\t]+/

    (* Define comments and empty lines *)
    let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . eol_no_spc ]
    let empty   = [ del /[ \t]*\n/ "\n" ]

    (* Define host *)
    let host_ip   = [ label "ip" . ip ]
    let host_fqdn = [ label "fqdn" . sep_spc . word ]

    let host_test_url  = [ label "url" . sep_spc . url ]
    let host_test_cont (kw:string) = [ sep_spc . store /!?/ . key kw .
                             (Util.del_str ";" .
                                [ label "url" . word ] .
			        (Util.del_str ";" . [ label "keyword" . word ])?
			     )?
			     ]

    let host_test_flag (kw:string) = [ sep_spc . store /!?/ . key kw ]

    let host_test = host_test_cont "cont"
                  | host_test_cont "contInsecure"
                  | host_test_cont "dns"
		  | host_test_flag "noping"
		  | host_test_flag "noconn"
		  | host_test_flag "ssh"
		  | host_test_flag "smtp"
		  | host_test_flag "pop3"
		  | host_test_flag "imap2"
		  | host_test_flag "telnet"
		  | host_test_flag "BBDISPLAY"
		  | host_test_flag "BBNET"
		  | host_test_flag "BBPAGER"
                  | host_test_url

    let host_opts = [ label "probes" . sep_spc . Util.del_str "#" . host_test* ]

    let host = [ label "host" . host_ip . host_fqdn . host_opts . eol ]

    (* Define group-compress and group-only *)
    let group_compress = [ key "group-compress" . sep_spc . value_to_eol . eol_no_spc .
                  ( comment | empty | host)*
		  ]

    let group_only_col  = [ label "col" . word ]
    let group_only_cols = sep_spc . group_only_col . ( Util.del_str "|" . group_only_col )*
    let group_only      = [ key "group-only" . group_only_cols . sep_spc . value_to_eol . eol_no_spc .
                  ( comment | empty | host)*
		  ]

    (* Define page *)
    let page_title = [ label "title" . sep_spc . value_to_eol . eol_no_spc ]
    let page = [ key "page" . sep_spc . word . page_title .
                  ( comment | empty | host )* . ( group_compress | group_only )*
		  ]

    (* Define lens *)

    let lns = (comment | empty)* . page*

    let filter = incl "/etc/bb/bb-hosts"
               . Util.stdexcl

    let xfm = transform lns filter