postfix_transport.aug also applies to virtual (5)

(*
Module: Postfix_Transport
  Parses /etc/postfix/transport

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

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

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/postfix/transport. See <filter>.

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

module Postfix_Transport =

autoload xfm

(* View: space_or_eol *)
let space_or_eol = del /([ \t]*\n)?[ \t]+/ " "

(* View: colon *)
let colon = Sep.colon

(* View: transport *)
let transport = [ label "transport" . (store Rx.word)? ]

(* View: nexthop *)
let nexthop = [ label "nexthop" . (store Rx.space_in)? ]

(* View: record *)
let record = [ label "pattern" . store /[A-Za-z0-9@\*.-]+/
             . space_or_eol . transport
             . colon . nexthop
             . Util.eol ]

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

(* Variable: filter *)
let filter = incl "/etc/postfix/transport"
           . incl "/etc/postfix/virtual"

let xfm = transform lns filter

(*
Module: Postfix_Transport
  Parses /etc/postfix/transport

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

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

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

About: Lens Usage
   To be documented

About: Configuration files
   This lens applies to /etc/postfix/transport. See <filter>.

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

module Postfix_Transport =

autoload xfm

(* View: space_or_eol *)
let space_or_eol = del /([ \t]*\n)?[ \t]+/ " "

(* View: colon *)
let colon = Sep.colon

(* View: transport *)
let transport = [ label "transport" . (store Rx.word)? ]

(* View: nexthop *)
let nexthop = [ label "nexthop" . (store Rx.space_in)? ]

(* View: record *)
let record = [ label "pattern" . store /[A-Za-z0-9@\*.-]+/
             . space_or_eol . transport
             . colon . nexthop
             . Util.eol ]

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

(* Variable: filter *)
let filter = incl "/etc/postfix/transport"

let xfm = transform lns filter


Allow Creation of Hostname When File is Missing

Within augtool, the current `hostname` lens does not allow you to create a hostname entry under the following circumstances:
* /etc/hostname file is empty
* /etc/hostname file does not exist

:: /etc/hostname file is empty

There is no augtool command(s) that will allow you to add a hostname entry to an existing, but empty, file.  Example:
```
$ sudo rm /etc/hostname
$ sudo touch /etc/hostname
$ sudo augtool

augtool> set /files/etc/hostname/hostname myhostname
augtool> save
error: Failed to execute command
saving failed (run 'errors' for details)

augtool> errors

Error in /etc/hostname:1.0 (parse_skel_failed)
  Input string does not match at all
  Lens: /usr/share/augeas/lenses/dist/hostname.aug:16.10-.57:
```

Additionally, you cannot remove the empty file using augtool:
```
$ sudo rm /etc/hostname
$ sudo touch /etc/hostname
$ sudo augtool rm /files/etc/hostname
$ ls -l /etc/hostname
-rw-r--r-- 1 root root 0 Feb 19 12:39 /etc/hostname
```

:: /etc/hostname does not exist

Similar to the empty file case, you cannot create a hostname entry if the file does not already exist.

You can, however, create an empty file:
```
$ sudo rm /etc/hostname
# You can create an empty file with clear or touch
$ sudo augtool touch /files/etc/hostname
$ ls -l /etc/hostname
-rw-r--r-- 1 root root 0 Feb 19 12:34 /etc/hostname
```

Of course, now that you have an empty file, you cannot create a hostname entry for it :(

This proposed change will allow you to at least create a hostname entry when the file does not already exist.

It also enables you to remove file, allowing for a deterministic approach:
```
$ sudo rm /etc/hostname
$ sudo touch /etc/hostname
$ sudo augtool rm /files/etc/hostname
$ ls -l /etc/hostname
ls: cannot access '/etc/hostname': No such file or directory
$ sudo augtool set /files/etc/hostname/hostname myhostname
$ ls -l /etc/hostname
-rw-r--r-- 1 root root 11 Feb 19 12:44 /etc/hostname
$ cat /etc/hostname
myhostname
```

NOTE: Its probably very possible to fix the lens to deal more of the edge cases, but this quick fix at least gives deterministic behavior if a rm/set each time I want to configure the hostname.

Thank you for your consideration.

-David Farrell

(*
Module: Hostname
  Parses /etc/hostname and /etc/mailname

Author: Raphael Pinson <raphink@gmail.com>

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

module Hostname =
autoload xfm

(* View: lns *)
let lns = [ label "hostname" . store Rx.word . Util.eol ] | Util.empty

(* View: filter *)
let filter = incl "/etc/hostname"
           . incl "/etc/mailname"

let xfm = transform lns filter

(*
Module: Hostname
  Parses /etc/hostname and /etc/mailname

Author: Raphael Pinson <raphink@gmail.com>

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

module Hostname =
autoload xfm

(* View: lns *)
let lns = [ label "hostname" . store Rx.word . Util.eol ]

(* View: filter *)
let filter = incl "/etc/hostname"
           . incl "/etc/mailname"

let xfm = transform lns filter


Add FreeBSD path for rc.conf.d

Add FreeBSD path for rc.conf.d

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

  (* Delete a blank line, rather than mapping it *)
  let del_empty = del (Util.empty_generic_re . "\n") "\n"

  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 brace_eol = del /[ \t\n]+/ "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9A-Za-z_,]+\])?/ - ("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  = /[^`;()'"&|\n\\# \t]#*|\\\\./
  let dquot =
       let char = /[^"\\]|\\\\./ | Rx.cl
    in "\"" . char* . "\""                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]+`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\n;]+``/
  let dollar_assign = /\$\([^\(\)#\n]*\)/
  let dollar_arithm = /\$\(\([^\)#\n]*\)\)/

  let anyquot = (char|dquot|squot|dollar_assign|dollar_arithm)+ | bquot | dbquot
  let sto_to_semicol = store (anyquot . (Rx.cl_or_space . anyquot)*)

  (* 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 anyquot 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 (anyquot | empty_array)?

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

  let var_action (name:string) =
    Util.indent . del name name . Util.del_ws_spc
    . label ("@" . name) . counter "var_action"
    . Build.opt_list [ seq "var_action" . store (key_re | matching_re) ] Util.del_ws_spc

  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]+/

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

  let eval =
    Util.indent . Util.del_str "eval" . Util.del_ws_spc
    . label "@eval" . store anyquot

  let alias =
    Util.indent . Util.del_str "alias" . Util.del_ws_spc
    . label "@alias" . store key_re . eq
    . [ label "value" . store anyquot ]

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

  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 )?

  let action (operator:string) (lbl:string) (sto:lens) =
       let sp = Rx.cl_or_opt_space | /[ \t\n]+/
    in [ del (sp . operator . sp) (" " . operator . " ")
       . label ("@".lbl) . sto ]

  let action_pipe = action "|" "pipe"
  let action_and = action "&&" "and"
  let action_or = action "||" "or"

  let condition =
    let cond (start:string) (end:string) = [ label "type" . store start ]
                                         . Util.del_ws_spc . sto_to_semicol
                                         . Util.del_ws_spc . Util.del_str end
                                         . ( action_and sto_to_semicol | action_or sto_to_semicol )*
    in Util.indent . label "@condition" . (cond "[" "]" | cond "[[" "]]")

  (* Entry types *)
  let entry_eol_item (item:lens) = [ item . comment_or_eol ]
  let entry_item (item:lens) = [ item ]

  let entry_eol_nocommand =
      entry_eol_item source
        | entry_eol_item kv
        | entry_eol_item unset
        | entry_eol_item bare_export
        | entry_eol_item builtin
        | entry_eol_item return
        | entry_eol_item condition
        | entry_eol_item eval
        | entry_eol_item alias

  let entry_noeol_nocommand =
      entry_item source
        | entry_item kv
        | entry_item unset
        | entry_item bare_export
        | entry_item builtin
        | entry_item return
        | entry_item condition
        | entry_item eval
        | entry_item alias

  (* Command *)
  let rec command =
       let env = [ key key_re . eq . store anyquot . Sep.cl_or_space ]
    in let reserved_key = /exit|shift|return|ulimit|unset|export|source|\.|if|for|select|while|until|then|else|fi|done|case|eval|alias/
    in let word = /[A-Za-z0-9_.-\/]+/
    in let entry_eol = entry_eol_nocommand | entry_eol_item command
    in let entry_noeol = entry_noeol_nocommand | entry_item command
    in let entry = entry_eol | entry_noeol
    in let pipe = action_pipe (entry_eol_item command | entry_item command)
    in let and = action_and entry
    in let or = action_or entry
    in Util.indent . label "@command" . env* . store (word - reserved_key)
     . [ Sep.cl_or_space . label "@arg" . sto_to_semicol]?
     . ( pipe | and | or )?

  let entry_eol = entry_eol_nocommand
                | entry_eol_item command

  let entry_noeol = entry_noeol_nocommand
                  | entry_item command

(************************************************************************
 * 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) (entry_noeol:lens) =
       let pattern = [ label "@pattern" . sto_to_semicol . Sep.opt_space ]
    in let case_entry = [ label "@case_entry"
                       . Util.indent . pattern
                       . (Util.del_str "|" . Sep.opt_space . pattern)*
                       . Util.del_str ")" . eol
                       . entry* . entry_noeol?
                       . 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 subshell (entry:lens) =
    [ Util.indent . label "@subshell"
    . Util.del_str "{" . brace_eol
    . entry+
    . Util.indent . Util.del_str "}" . eol ]

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

  let rec rec_entry =
    let entry = comment | entry_eol | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry entry_noeol
      | function entry
      | subshell entry

  let lns_norec = del_empty* . (comment | entry_eol) *

  let lns = del_empty* . (comment | entry_eol | 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 "ip*tables.save" .
      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_excl "network/if-up.d/SuSEfirewall2" .
      sc_incl "network/providers/*" .
      sc_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE" .
      sc_excl "*.systemd"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/rmt"
                     . excl "/etc/default/star"
                     . excl "/etc/default/whoopsie"
                     . incl "/etc/profile"
                     . incl "/etc/profile.d/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/conf.d/*"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/firewalld/firewalld.conf"
                     . 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/kamailio/kamctlrc"
                     . incl "/etc/lbu/lbu.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/periodic.conf"
                     . incl "/etc/popularity-contest.conf"
                     . incl "/etc/rc.conf"
                     . incl "/etc/rc.conf.d/*"
                     . incl "/etc/rc.conf.local"
                     . incl "/etc/selinux/config"
                     . incl "/etc/ucf.conf"
                     . incl "/etc/locale.conf"
                     . incl "/etc/vconsole.conf"
                     . incl "/etc/byobu/*"

  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

  (* Delete a blank line, rather than mapping it *)
  let del_empty = del (Util.empty_generic_re . "\n") "\n"

  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 brace_eol = del /[ \t\n]+/ "\n"

  let key_re = /[A-Za-z0-9_]+(\[[0-9A-Za-z_,]+\])?/ - ("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  = /[^`;()'"&|\n\\# \t]#*|\\\\./
  let dquot =
       let char = /[^"\\]|\\\\./ | Rx.cl
    in "\"" . char* . "\""                    (* " Emacs, relax *)
  let squot = /'[^']*'/
  let bquot = /`[^`\n]+`/
  (* dbquot don't take spaces or semi-colons *)
  let dbquot = /``[^` \t\n;]+``/
  let dollar_assign = /\$\([^\(\)#\n]*\)/
  let dollar_arithm = /\$\(\([^\)#\n]*\)\)/

  let anyquot = (char|dquot|squot|dollar_assign|dollar_arithm)+ | bquot | dbquot
  let sto_to_semicol = store (anyquot . (Rx.cl_or_space . anyquot)*)

  (* 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 anyquot 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 (anyquot | empty_array)?

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

  let var_action (name:string) =
    Util.indent . del name name . Util.del_ws_spc
    . label ("@" . name) . counter "var_action"
    . Build.opt_list [ seq "var_action" . store (key_re | matching_re) ] Util.del_ws_spc

  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]+/

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

  let eval =
    Util.indent . Util.del_str "eval" . Util.del_ws_spc
    . label "@eval" . store anyquot

  let alias =
    Util.indent . Util.del_str "alias" . Util.del_ws_spc
    . label "@alias" . store key_re . eq
    . [ label "value" . store anyquot ]

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

  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 )?

  let action (operator:string) (lbl:string) (sto:lens) =
       let sp = Rx.cl_or_opt_space | /[ \t\n]+/
    in [ del (sp . operator . sp) (" " . operator . " ")
       . label ("@".lbl) . sto ]

  let action_pipe = action "|" "pipe"
  let action_and = action "&&" "and"
  let action_or = action "||" "or"

  let condition =
    let cond (start:string) (end:string) = [ label "type" . store start ]
                                         . Util.del_ws_spc . sto_to_semicol
                                         . Util.del_ws_spc . Util.del_str end
                                         . ( action_and sto_to_semicol | action_or sto_to_semicol )*
    in Util.indent . label "@condition" . (cond "[" "]" | cond "[[" "]]")

  (* Entry types *)
  let entry_eol_item (item:lens) = [ item . comment_or_eol ]
  let entry_item (item:lens) = [ item ]

  let entry_eol_nocommand =
      entry_eol_item source
        | entry_eol_item kv
        | entry_eol_item unset
        | entry_eol_item bare_export
        | entry_eol_item builtin
        | entry_eol_item return
        | entry_eol_item condition
        | entry_eol_item eval
        | entry_eol_item alias

  let entry_noeol_nocommand =
      entry_item source
        | entry_item kv
        | entry_item unset
        | entry_item bare_export
        | entry_item builtin
        | entry_item return
        | entry_item condition
        | entry_item eval
        | entry_item alias

  (* Command *)
  let rec command =
       let env = [ key key_re . eq . store anyquot . Sep.cl_or_space ]
    in let reserved_key = /exit|shift|return|ulimit|unset|export|source|\.|if|for|select|while|until|then|else|fi|done|case|eval|alias/
    in let word = /[A-Za-z0-9_.-\/]+/
    in let entry_eol = entry_eol_nocommand | entry_eol_item command
    in let entry_noeol = entry_noeol_nocommand | entry_item command
    in let entry = entry_eol | entry_noeol
    in let pipe = action_pipe (entry_eol_item command | entry_item command)
    in let and = action_and entry
    in let or = action_or entry
    in Util.indent . label "@command" . env* . store (word - reserved_key)
     . [ Sep.cl_or_space . label "@arg" . sto_to_semicol]?
     . ( pipe | and | or )?

  let entry_eol = entry_eol_nocommand
                | entry_eol_item command

  let entry_noeol = entry_noeol_nocommand
                  | entry_item command

(************************************************************************
 * 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) (entry_noeol:lens) =
       let pattern = [ label "@pattern" . sto_to_semicol . Sep.opt_space ]
    in let case_entry = [ label "@case_entry"
                       . Util.indent . pattern
                       . (Util.del_str "|" . Sep.opt_space . pattern)*
                       . Util.del_str ")" . eol
                       . entry* . entry_noeol?
                       . 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 subshell (entry:lens) =
    [ Util.indent . label "@subshell"
    . Util.del_str "{" . brace_eol
    . entry+
    . Util.indent . Util.del_str "}" . eol ]

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

  let rec rec_entry =
    let entry = comment | entry_eol | rec_entry in
        cond_if entry
      | loop_for entry
      | loop_select entry
      | loop_while entry
      | loop_until entry
      | case entry entry_noeol
      | function entry
      | subshell entry

  let lns_norec = del_empty* . (comment | entry_eol) *

  let lns = del_empty* . (comment | entry_eol | 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 "ip*tables.save" .
      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_excl "network/if-up.d/SuSEfirewall2" .
      sc_incl "network/providers/*" .
      sc_excl "network-scripts" .
      sc_incl "network-scripts/ifcfg-*" .
      sc_excl "rhn" .
      sc_incl "rhn/allowed-actions/*" .
      sc_excl "rhn/allowed-actions/script" .
      sc_incl "rhn/allowed-actions/script/*" .
      sc_incl "rhn/rhnsd" .
      sc_excl "SuSEfirewall2.d" .
      sc_incl "SuSEfirewall2.d/cobbler" .
      sc_incl "SuSEfirewall2.d/services/*" .
      sc_excl "SuSEfirewall2.d/services/TEMPLATE" .
      sc_excl "*.systemd"

  let filter_default = incl "/etc/default/*"
                     . excl "/etc/default/grub_installdevice*"
                     . excl "/etc/default/rmt"
                     . excl "/etc/default/star"
                     . excl "/etc/default/whoopsie"
                     . incl "/etc/profile"
                     . incl "/etc/profile.d/*"
  let filter_misc    = incl "/etc/arno-iptables-firewall/debconf.cfg"
                     . incl "/etc/conf.d/*"
                     . incl "/etc/cron-apt/config"
                     . incl "/etc/environment"
                     . incl "/etc/firewalld/firewalld.conf"
                     . 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/kamailio/kamctlrc"
                     . incl "/etc/lbu/lbu.conf"
                     . incl "/etc/lintianrc"
                     . incl "/etc/lsb-release"
                     . incl "/etc/os-release"
                     . incl "/etc/periodic.conf"
                     . 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"
                     . incl "/etc/byobu/*"

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

  let xfm = transform lns filter

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


Reflect Ubuntu 16.04 location of php.ini (#399)

php.ini has moved with upgrade from PHP5 to PHP7 (included in Ubuntu 16.04).

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

module PHP =
  autoload xfm

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

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

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

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

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

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

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

let xfm = transform lns filter

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

module PHP =
  autoload xfm

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

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

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

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

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

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

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

let xfm = transform lns filter


Rx: remove \b and \d escapes

Rx: remove \b and \d escapes


Remove leading whitespace inside Match directives

# /etc/sshd/sshd_config

map
  grammar sshd
  include '/etc/ssh/sshd_config' '/system/config/sshd'
end

grammar sshd
  token EOL /[ \t]*\n/ = '\n'
  token SEP /[ \t]+(?!\n)/ = ' '
  token COMMENT /(#.*|[ \t]*)\n/ = '\n'

  start: (comment | accept_env | allow_groups | allow_users
          | deny_groups | macs | match | other_entry ) *

  comment: [ COMMENT ]

  other_entry: [ key ... . SEP . store ... . EOL ]

  accept_env: [ key 'AcceptEnv' .
              ([ SEP . seq 'accept_env' . store ...])* .
              EOL]

  allow_groups: [ key 'AllowGroups' .
              ([ SEP . seq 'allow_groups' . store ...])* .
              EOL]

  allow_users: [ key 'AllowUsers' .
              ([ SEP . seq 'allow_users' . store ...])* .
              EOL]

  deny_groups: [ key 'DenyGroups' .
              ([ SEP . seq 'deny_groups' . store ...])* .
              EOL]

  deny_users: [ key 'DenyUsers' .
              ([ SEP . seq 'deny_users' . store ...])* .
              EOL]

  macs: [ key 'MACs' . SEP .
           [ seq 'macs' . store ... ] .
           ([ seq 'macs' . ',' . store ...])* .
          EOL ]

  match: [ key 'Match' .
           [ seq 'match' .
             [ label 'cond' . store ... . EOL ] .
             (SEP . other_entry) * ] ]

end

# /etc/sshd/sshd_config

map
  grammar sshd
  include '/etc/ssh/sshd_config' '/system/config/sshd'
end

grammar sshd
  token EOL /[ \t]*\n/ = '\n'
  token SEP /[ \t]+(?!\n)/ = ' '
  token COMMENT /(#.*|[ \t]*)\n/ = '\n'

  start: (comment | accept_env | allow_groups | allow_users
          | deny_groups | macs | match | other_entry ) *

  comment: [ COMMENT ]

  other_entry: [ key ... . SEP . store ... . EOL ]

  accept_env: [ key 'AcceptEnv' .
              ([ SEP . seq 'accept_env' . store ...])* .
              EOL]

  allow_groups: [ key 'AllowGroups' .
              ([ SEP . seq 'allow_groups' . store ...])* .
              EOL]

  allow_users: [ key 'AllowUsers' .
              ([ SEP . seq 'allow_users' . store ...])* .
              EOL]

  deny_groups: [ key 'DenyGroups' .
              ([ SEP . seq 'deny_groups' . store ...])* .
              EOL]

  deny_users: [ key 'DenyUsers' .
              ([ SEP . seq 'deny_users' . store ...])* .
              EOL]

  macs: [ key 'MACs' . SEP .
           [ seq 'macs' . store ... ] .
           ([ seq 'macs' . ',' . store ...])* .
          EOL ]

  match: [ key 'Match' .
           [ seq 'match' .
             [ label 'cond' . store ... . EOL ] .
             (other_entry) * ] ]

end


Slapd: Add unit test for by/control

Slapd: Add unit test for by/control

module Test_slapd =

let conf = "# This is the main slapd configuration file. See slapd.conf(5) for more
# info on the configuration options.

#######################################################################
# Global Directives:

# Features to permit
#allow bind_v2

# Schema and objectClass definitions
include         /etc/ldap/schema/core.schema

#######################################################################
# Specific Directives for database #1, of type hdb:
# Database specific directives apply to this databasse until another
# 'database' directive occurs
database        hdb

# The base of your directory in database #1
suffix          \"dc=nodomain\"

access to attrs=userPassword,shadowLastChange
        by dn=\"cn=admin,dc=nodomain\" write
        by anonymous auth
        by self write
        by * none
"

test Slapd.lns get conf =
  { "#comment" = "This is the main slapd configuration file. See slapd.conf(5) for more" }
  { "#comment" = "info on the configuration options." }
  {}
  { "#comment" = "######################################################################" }
  { "#comment" = "Global Directives:"}
  {}
  { "#comment" = "Features to permit" }
  { "#comment" = "allow bind_v2" }
  {}
  { "#comment" = "Schema and objectClass definitions" }
  { "include"  = "/etc/ldap/schema/core.schema" }
  {}
  { "#comment" = "######################################################################" }
  { "#comment" = "Specific Directives for database #1, of type hdb:" }
  { "#comment" = "Database specific directives apply to this databasse until another" }
  { "#comment" = "'database' directive occurs" }
  { "database" = "hdb"
     {}
     { "#comment" = "The base of your directory in database #1" }
     { "suffix"   = "\"dc=nodomain\"" }
     {}
     { "access to" = "attrs=userPassword,shadowLastChange"
        { "by"
           { "who" = "dn=\"cn=admin,dc=nodomain\"" }
           { "what" = "write" } }
        { "by"
           { "who" = "anonymous" }
           { "what" = "auth" } }
        { "by"
           { "who" = "self" }
           { "what" = "write" } }
        { "by"
           { "who" = "*" }
           { "what" = "none" } } } }

test Slapd.lns get "access to dn.subtree=\"dc=example,dc=com\"
  by self write stop\n" =
  { "access to" = "dn.subtree=\"dc=example,dc=com\""
    { "by"
      { "who" = "self" }
      { "what" = "write" }
      { "control" = "stop" } } }


Revert "switched the httpd.aug to the version from 1.2"

This reverts commit 60fce0b22e422bbfd1bb700740c9648d209c0228.

(* Apache HTTPD lens for Augeas

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

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

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

About: Lens Usage
  Sample usage of this lens in augtool

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

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

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

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

*)

module Httpd =

autoload xfm

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

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

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

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

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

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^\\ '"\t\r\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let cdot = /\\\\./
let cl = /\\\\\n/
let dquot =
     let no_dquot = /[^"\\\r\n]/
  in /"/ . (no_dquot|cdot|cl)* . /"/
let squot =
     let no_squot = /[^'\\\r\n]/
  in /'/ . (no_squot|cdot|cl)* . /'/
let comp = /[<>=]?=/

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

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

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

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

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

let rec content = section (content|directive)

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

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

let xfm = transform lns filter

(* Apache HTTPD lens for Augeas

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

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

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

About: Lens Usage
  Sample usage of this lens in augtool

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

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

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

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

*)

module Httpd =

autoload xfm

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let xfm = transform lns filter


test_php.aug: make a nice test for PHP

test_php.aug: make a nice test for PHP

module Test_php =

   let conf = "[PHP]
; Enable the PHP scripting language engine under Apache.
engine = On

; Enable compatibility mode with Zend Engine 1 (PHP 4.x)
zend.ze1_compatibility_mode = Off
unserialize_callback_func=
date.default_latitude = 31.7667

[sqlite]
sqlite.assoc_case = 0
"

   test PHP.lns get conf =
      { "section" = "PHP"
         { "#comment" = "Enable the PHP scripting language engine under Apache." }
	 { "engine"  = "On" }
	 {}
	 { "#comment" = "Enable compatibility mode with Zend Engine 1 (PHP 4.x)" }
	 { "zend.ze1_compatibility_mode" = "Off" }
	 { "unserialize_callback_func" }
	 { "date.default_latitude" = "31.7667" }
	 {} }
      { "section" = "sqlite"
         { "sqlite.assoc_case" = "0" } }

module Test_php =

   let conf = "[PHP]

;;;;;;;;;;;;;;;;;;;;
; Language Options ;
;;;;;;;;;;;;;;;;;;;;

; Enable the PHP scripting language engine under Apache.
engine = On

; Enable compatibility mode with Zend Engine 1 (PHP 4.x)
zend.ze1_compatibility_mode = Off

; Allow the <? tag.  Otherwise, only <?php and <script> tags are recognized.
; NOTE: Using short tags should be avoided when developing applications or
; libraries that are meant for redistribution, or deployment on PHP
; servers which are not under your control, because short tags may not
; be supported on the target server. For portable, redistributable code,
; be sure not to use short tags.
short_open_tag = On

; Allow ASP-style <% %> tags.
asp_tags = Off

; The number of significant digits displayed in floating point numbers.
precision    =  12

; Enforce year 2000 compliance (will cause problems with non-compliant browsers)
y2k_compliance = On

; Output buffering allows you to send header lines (including cookies) even
; after you send body content, at the price of slowing PHP's output layer a
; bit.  You can enable output buffering during runtime by calling the output
; buffering functions.  You can also enable output buffering for all files by
; setting this directive to On.  If you wish to limit the size of the buffer
; to a certain size - you can use a maximum number of bytes instead of 'On', as
; a value for this directive (e.g., output_buffering=4096).
output_buffering = Off
; You can redirect all of the output of your scripts to a function.  For
; example, if you set output_handler to \"mb_output_handler\", character
; encoding will be transparently converted to the specified encoding.
; Setting any output handler automatically turns on output buffering.
; Note: People who wrote portable scripts should not depend on this ini
;       directive. Instead, explicitly set the output handler using ob_start().
;       Using this ini directive may cause problems unless you know what script
;       is doing.
; Note: You cannot use both \"mb_output_handler\" with \"ob_iconv_handler\"
;       and you cannot use both \"ob_gzhandler\" and \"zlib.output_compression\".
; Note: output_handler must be empty if this is set 'On' !!!!
;       Instead you must use zlib.output_handler.
;output_handler =

; Transparent output compression using the zlib library
; Valid values for this option are 'off', 'on', or a specific buffer size
; to be used for compression (default is 4KB)
; Note: Resulting chunk size may vary due to nature of compression. PHP
;       outputs chunks that are few hundreds bytes each as a result of
;       compression. If you prefer a larger chunk size for better
;       performance, enable output_buffering in addition.
; Note: You need to use zlib.output_handler instead of the standard
;       output_handler, or otherwise the output will be corrupted.
zlib.output_compression = Off
;zlib.output_compression_level = -1

; You cannot specify additional output handlers if zlib.output_compression
; is activated here. This setting does the same as output_handler but in
; a different order.
;zlib.output_handler =

; Implicit flush tells PHP to tell the output layer to flush itself
; automatically after every output block.  This is equivalent to calling the
; PHP function flush() after each and every call to print() or echo() and each
; and every HTML block.  Turning this option on has serious performance
; implications and is generally recommended for debugging purposes only.
implicit_flush = Off

; The unserialize callback function will be called (with the undefined class'
; name as parameter), if the unserializer finds an undefined class
; which should be instantiated.
; A warning appears if the specified function is not defined, or if the
; function doesn't include/implement the missing class.
; So only set this entry, if you really want to implement such a
; callback-function.
unserialize_callback_func=

;extension=mysql.so
;extension=gd.so

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;

[Date]
; Defines the default timezone used by the date functions
;date.timezone =

;date.default_latitude = 31.7667
;date.default_longitude = 35.2333

;date.sunrise_zenith = 90.583333
;date.sunset_zenith = 90.583333

[filter]
;filter.default = unsafe_raw
;filter.default_flags =

[iconv]
;iconv.input_encoding = ISO-8859-1
;iconv.internal_encoding = ISO-8859-1
;iconv.output_encoding = ISO-8859-1

[sqlite]
;sqlite.assoc_case = 0

"

   test PHP.lns get conf = ?


qemu augeas: Add spice_tls/spice_tls_x509_cert_dir

If vdsm is installed and configured in Fedora 17, we add the following
items into qemu.conf:

spice_tls=1
spice_tls_x509_cert_dir="/etc/pki/vdsm/libvirt-spice"

However, after this changes, augtool cannot identify qemu.conf anymore.

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

module Libvirtd_qemu =
   autoload xfm

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

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

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

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

   (* Config entry grouped by function - same order as example config *)
   let vnc_entry = str_entry "vnc_listen"
                 | bool_entry "vnc_auto_unix_socket"
                 | bool_entry "vnc_tls"
                 | str_entry "vnc_tls_x509_cert_dir"
                 | bool_entry "vnc_tls_x509_verify"
                 | str_entry "vnc_password"
                 | bool_entry "vnc_sasl"
                 | str_entry "vnc_sasl_dir"
                 | str_entry "spice_listen"
                 | bool_entry "spice_tls"
                 | str_entry  "spice_tls_x509_cert_dir"
                 | str_entry "spice_passwd"
                 | str_entry "security_driver"
                 | bool_entry "security_default_confined"
                 | bool_entry "security_require_confined"
                 | str_entry "user"
                 | str_entry "group"
                 | bool_entry "dynamic_ownership"
                 | str_array_entry "cgroup_controllers"
                 | str_array_entry "cgroup_device_acl"
                 | str_entry "save_image_format"
                 | str_entry "dump_image_format"
                 | str_entry "auto_dump_path"
                 | bool_entry "auto_dump_bypass_cache"
                 | bool_entry "auto_start_bypass_cache"
                 | str_entry "hugetlbfs_mount"
                 | bool_entry "mac_filter"
                 | bool_entry "relaxed_acs_check"
                 | bool_entry "vnc_allow_host_audio"
                 | bool_entry "clear_emulator_capabilities"
                 | bool_entry "allow_disk_format_probing"
                 | bool_entry "set_process_name"
                 | int_entry "max_processes"
                 | int_entry "max_files"
                 | str_entry "lock_manager"
                 | int_entry "max_queued"
                 | int_entry "keepalive_interval"
                 | int_entry "keepalive_count"

   (* Each enty in the config is one of the following three ... *)
   let entry = vnc_entry
   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
   let empty = [ label "#empty" . eol ]

   let record = indent . entry . eol

   let lns = ( record | comment | empty ) *

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

   let xfm = transform lns filter

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

module Libvirtd_qemu =
   autoload xfm

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

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

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

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

   (* Config entry grouped by function - same order as example config *)
   let vnc_entry = str_entry "vnc_listen"
                 | bool_entry "vnc_auto_unix_socket"
                 | bool_entry "vnc_tls"
                 | str_entry "vnc_tls_x509_cert_dir"
                 | bool_entry "vnc_tls_x509_verify"
                 | str_entry "vnc_password"
                 | bool_entry "vnc_sasl"
                 | str_entry "vnc_sasl_dir"
                 | str_entry "security_driver"
                 | bool_entry "security_default_confined"
                 | bool_entry "security_require_confined"
                 | str_entry "user"
                 | str_entry "group"
                 | bool_entry "dynamic_ownership"
                 | str_array_entry "cgroup_controllers"
                 | str_array_entry "cgroup_device_acl"
                 | str_entry "save_image_format"
                 | str_entry "dump_image_format"
                 | str_entry "auto_dump_path"
                 | bool_entry "auto_dump_bypass_cache"
                 | bool_entry "auto_start_bypass_cache"
                 | str_entry "hugetlbfs_mount"
                 | bool_entry "relaxed_acs_check"
                 | bool_entry "vnc_allow_host_audio"
                 | bool_entry "clear_emulator_capabilities"
                 | bool_entry "allow_disk_format_probing"
                 | bool_entry "set_process_name"
                 | int_entry "max_processes"
                 | int_entry "max_files"
                 | str_entry "lock_manager"
                 | int_entry "max_queued"
                 | int_entry "keepalive_interval"
                 | int_entry "keepalive_count"

   (* Each enty in the config is one of the following three ... *)
   let entry = vnc_entry
   let comment = [ label "#comment" . del /#[ \t]*/ "# " .  store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ]
   let empty = [ label "#empty" . eol ]

   let record = indent . entry . eol

   let lns = ( record | comment | empty ) *

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

   let xfm = transform lns filter


Changes in nrpe.aug Remove wrong ^ in comment regex Use Util.comment_generic Use generic modules Improve NaturalDocs documentation

(*
Module: Nrpe
  Parses nagios-nrpe configuration files.

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

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

module Nrpe =
  autoload xfm

let eol = Util.eol
let eq = Sep.equal

(* View: word *)
let word = /[^=\n\t ]+/

(* View: command
    nrpe.cfg usually has many entries defining commands to run

    > command[check_foo]=/path/to/nagios/plugin -w 123 -c 456
    > command[check_bar]=/path/to/another/nagios/plugin --option
*)
let command =
  let obrkt = del /\[/ "[" in
  let cbrkt = del /\]/ "]" in
    [ key "command" .
    [ obrkt . key /[^]\/\n]+/ . cbrkt . eq
            . store /[^=\n]+/ . del /\n/ "\n" ]
    ]

(* View: item_re
     regular entries re *)
let item_re = "server_port"
            | "command_prefix"
            | "server_address"
            | "allowed_hosts"
            | "debug"
            | "nrpe_user"
            | "nrpe_group"
            | "dont_blame_nrpe"
            | "command_timeout"
            | "connection_timeout"
            | "allow_weak_random_seed"
            | "pid_file"
            | "log_facility"

(* View: item
     regular entries *)
let item = [ key item_re . eq . store word . eol ]

(* View: include
    An include entry.

    nrpe.cfg can include more than one file or directory of files

    > include=/path/to/file1.cfg
    > include=/path/to/file2.cfg
*)
let include = [ key "include" .
  [ label "file" . eq . store word . eol ]
]

(* View: include_dir
    > include_dir=/path/to/dir/
*)
let include_dir = [ key "include_dir" .
  [ label "dir" . eq . store word . eol ]
]

(* View: comment
    Nrpe comments must start at beginning of line *)
let comment = Util.comment_generic /#[ \t]*/ "# "

(* blank lines and empty comments *)
let empty = Util.empty

(* View: lns
    The Nrpe lens *)
let lns = ( command | item | include | include_dir | comment | empty ) *

(* View: filter
    File filter *)
let filter = Util.stdexcl .
             incl "/etc/nrpe.cfg" .
             incl "/etc/nagios/nrpe.cfg"

let xfm = transform lns (filter)

(*
Module: Nrpe
  Parses nagios-nrpe configuration files.

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

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

module Nrpe =
  autoload xfm

let nl = del /[ \t]*\n/ "\n"
let eq = del /=/ "="

(* command entries

nrpe.cfg usually has many entries defining commands to run

command[check_foo]=/path/to/nagios/plugin -w 123 -c 456
command[check_bar]=/path/to/another/nagios/plugin --option

*)
let obrkt = del /\[/ "["
let cbrkt = del /\]/ "]"

let command = [ key "command" .
  [ obrkt . key /[^]\/\n]+/ . cbrkt . eq . store /[^=\n]+/ . del /\n/ "\n" ]
]

(* regular entries *)
let item_re = "server_port"
            | "command_prefix"
            | "server_address"
            | "allowed_hosts"
            | "debug"
            | "nrpe_user"
            | "nrpe_group"
            | "dont_blame_nrpe"
            | "command_timeout"
            | "connection_timeout"
            | "allow_weak_random_seed"
            | "pid_file"
            | "log_facility"

let item = [ key item_re . eq . store /[^=\n\t ]+/ . nl ]

(* include entries

nrpe.cfg can include more than one file or directory of files

include=/path/to/file1.cfg
include=/path/to/file2.cfg
include_dir=/path/to/dir/

*)
let include = [ key "include" .
  [ label "file" . eq . store /[^=\n\t ]+/ . nl ]
]
let include_dir = [ key "include_dir" .
  [ label "dir" . eq . store /[^=\n\t ]+/ . nl ]
]

(* comments

We can't use Util.comment because nrpe only allows comments starting at
beginning of line.

*)
let comment = [ label "#comment" . del /^#[ \t]*/ "# " . store /([^ \t\n].*[^ \t\n]|[^ \t\n])/ . nl ]

(* blank lines and empty comments *)
let empty = Util.empty

(* final lens definition *)
let lns = ( command | item | include | include_dir | comment | empty ) *

let filter = Util.stdexcl .
             incl "/etc/nrpe.cfg" .
             incl "/etc/nagios/nrpe.cfg"

let xfm = transform lns (filter)


Simplify Cron.key_re to improve typechecking of cron.aug

(*
Module: Cron
 Parses /etc/cron.d/*, /etc/crontab

Author: Raphael Pinson <raphink@gmail.com>

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

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

About: Lens Usage
  Sample usage of this lens in augtool

    * Get the entry that launches '/usr/bin/ls'
      >  match '/files/etc/crontab/entry[. = "/usr/bin/ls"]'

About: Configuration files
  This lens applies to /etc/cron.d/* and /etc/crontab. See <filter>.
*)

module Cron =
  autoload xfm

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

(* Group: Generic primitives *)

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

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

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

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

(* Variable: num *)
let num        = /[0-9*][0-9\/,*-]*/

(* Variable: alpha *)
let alpha      = /[A-Za-z]{3}/

(* Variable: alphanum *)
let alphanum   = (num|alpha) . ("-" . (num|alpha))?

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc = Util.del_ws_spc

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

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

(************************************************************************
 * View: shellvar
 *   A shell variable in crontab
 *************************************************************************)

let shellvar =
  let key_re = /[A-Za-z-1-9_]+(\[[0-9]+\])?/ - "entry" in
  let sto_to_eol = store /[^\n]*[^ \t\n]/ in
  [ key key_re . sep_eq . sto_to_eol . eol ]

(* View: minute *)
let minute     = [ label "minute"       . store num ]

(* View: hour *)
let hour       = [ label "hour"         . store num ]

(* View: dayofmonth *)
let dayofmonth = [ label "dayofmonth" . store num ]

(* View: month *)
let month      = [ label "month"        . store alphanum ]

(* View: dayofweek *)
let dayofweek  = [ label "dayofweek"  . store alphanum ]

(* View: user *)
let user       = [ label "user"         . store Rx.word ]

(************************************************************************
 * View: time
 *   Time in the format "minute hour dayofmonth month dayofweek"
 *************************************************************************)
let time        = [ label "time" .
                  minute . sep_spc . hour  . sep_spc . dayofmonth
                         . sep_spc . month . sep_spc . dayofweek ]

(* Variable: the valid values for schedules *)
let schedule_re = "reboot" | "yearly" | "annually" | "monthly"
                | "weekly" | "daily"  | "midnight" | "hourly"

(************************************************************************
 * View: schedule
 *   Time in the format "@keyword"
 *************************************************************************)
let schedule    = [ label "schedule" . Util.del_str "@"
                   . store schedule_re ]

(************************************************************************
 * View: entry
 *   A crontab entry
 *************************************************************************)

let entry       = [ label "entry" . indent
                   . ( time | schedule )
                   . sep_spc . user
                   . sep_spc . store Rx.space_in . eol ]

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

(* Variable: filter *)
let filter =
  incl "/etc/cron.d/*" .
  incl "/etc/crontab" .
  excl "/etc/cron.d/at.allow" .
  excl "/etc/cron.d/at.deny" .
  excl "/etc/cron.d/cron.allow" .
  excl "/etc/cron.d/cron.deny" .
  Util.stdexcl

let xfm = transform lns filter

(*
Module: Cron
 Parses /etc/cron.d/*, /etc/crontab

Author: Raphael Pinson <raphink@gmail.com>

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

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

About: Lens Usage
  Sample usage of this lens in augtool

    * Get the entry that launches '/usr/bin/ls'
      >  match '/files/etc/crontab/entry[. = "/usr/bin/ls"]'

About: Configuration files
  This lens applies to /etc/cron.d/* and /etc/crontab. See <filter>.
*)

module Cron =
  autoload xfm

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

(* Group: Generic primitives *)

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

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

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

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

(* Variable: num *)
let num        = /[0-9*][0-9\/,*-]*/

(* Variable: alpha *)
let alpha      = /[A-Za-z]{3}/

(* Variable: alphanum *)
let alphanum   = (num|alpha) . ("-" . (num|alpha))?

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc = Util.del_ws_spc

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

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

(************************************************************************
 * View: shellvar
 *   A shell variable in crontab
 *************************************************************************)

let shellvar =
  let key_re = Shellvars.key_re - "entry" in
  let sto_to_eol = store /[^\n]*[^ \t\n]/ in
  [ key key_re . sep_eq . sto_to_eol . eol ]

(* View: minute *)
let minute     = [ label "minute"       . store num ]

(* View: hour *)
let hour       = [ label "hour"         . store num ]

(* View: dayofmonth *)
let dayofmonth = [ label "dayofmonth" . store num ]

(* View: month *)
let month      = [ label "month"        . store alphanum ]

(* View: dayofweek *)
let dayofweek  = [ label "dayofweek"  . store alphanum ]

(* View: user *)
let user       = [ label "user"         . store Rx.word ]

(************************************************************************
 * View: time
 *   Time in the format "minute hour dayofmonth month dayofweek"
 *************************************************************************)
let time        = [ label "time" .
                  minute . sep_spc . hour  . sep_spc . dayofmonth
                         . sep_spc . month . sep_spc . dayofweek ]

(* Variable: the valid values for schedules *)
let schedule_re = "reboot" | "yearly" | "annually" | "monthly"
                | "weekly" | "daily"  | "midnight" | "hourly"

(************************************************************************
 * View: schedule
 *   Time in the format "@keyword"
 *************************************************************************)
let schedule    = [ label "schedule" . Util.del_str "@"
                   . store schedule_re ]

(************************************************************************
 * View: entry
 *   A crontab entry
 *************************************************************************)

let entry       = [ label "entry" . indent
                   . ( time | schedule )
                   . sep_spc . user
                   . sep_spc . store Rx.space_in . eol ]

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

(* Variable: filter *)
let filter =
  incl "/etc/cron.d/*" .
  incl "/etc/crontab" .
  excl "/etc/cron.d/at.allow" .
  excl "/etc/cron.d/at.deny" .
  excl "/etc/cron.d/cron.allow" .
  excl "/etc/cron.d/cron.deny" .
  Util.stdexcl

let xfm = transform lns filter


Samba: Change default indentation and separation

Match indentation of the example configuration file shipped by the upstream
authors


Remove bogus reset of 'record' counter

Remove bogus reset of 'record' counter

# Parsing /etc/inittab

map
  grammar inittab
  include '/etc/inittab' '/system/config/inittab'
end

grammar inittab

  token SEP ':'
  token EOL '\n'

  file: ( comment | record ) *

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

  record: [ seq 'record' .
            [ label 'id' . store ..? ] .
            SEP .
            [ label 'runlevels' . store ..? ] .
            SEP .
            [ label 'action' . store ..? ] .
            SEP .
            [ label 'process' . store ..? ] .
            EOL
          ]
end

# Parsing /etc/inittab

#map
#  grammar inittab
#  include '/etc/inittab' '/system/config/inittab'
#end

grammar inittab

  token SEP ':'
  token EOL '\n'

  file: ( comment | record ) *

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

  record: counter 'record' .
          [ seq 'record' .
            [ label 'id' . store ..? ] .
            SEP .
            [ label 'runlevels' . store ..? ] .
            SEP .
            [ label 'action' . store ..? ] .
            SEP .
            [ label 'process' . store ..? ] .
            EOL
          ]
end


OpenShift_Quickstarts: Fix tests after Json update

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

module Test_OpenShift_Quickstarts =

(* Variable: conf *)
let conf = "[
  {\"quickstart\": {
    \"id\": \"1\",
    \"name\":\"CakePHP\",
    \"website\":\"http://cakephp.org/\",
    \"initial_git_url\":\"git://github.com/openshift/cakephp-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC.\",
    \"tags\":[\"php\",\"cakephp\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\": {
    \"id\": \"2\",
    \"name\":\"Django\",
    \"website\":\"https://www.djangoproject.com/\",
    \"initial_git_url\":\"git://github.com/openshift/django-example.git\",
    \"cartridges\":[\"python-2.7\"],
    \"summary\":\"A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"python\",\"django\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\":{
    \"id\": \"4\",
    \"name\":\"Drupal\",
    \"website\":\"http://drupal.org/\",
    \"initial_git_url\":\"git://github.com/openshift/drupal-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"drupal\",\"wiki\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"6\",
    \"name\":\"Ruby on Rails\",
    \"website\":\"http://rubyonrails.org/\",
    \"initial_git_url\":\"git://github.com/openshift/rails-example.git\",
    \"cartridges\":[\"ruby-1.9\",\"mysql-5.1\"],
    \"summary\":\"An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.\",
    \"tags\":[\"ruby\",\"rails\",\"framework\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"8\",
    \"name\":\"WordPress\",
    \"website\":\"http://wordpress.org\",
    \"initial_git_url\":\"git://github.com/openshift/wordpress-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"wordpress\",\"blog\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }}
]"

(* Variable: new_conf *)
let new_conf = "[
  {\"quickstart\": {
    \"id\": \"1\",
    \"name\":\"CakePHP\",
    \"website\":\"http://cakephp.org/\",
    \"initial_git_url\":\"git://github.com/openshift/cakephp-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC.\",
    \"tags\":[\"php\",\"cakephp\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\": {
    \"id\": \"2\",
    \"name\":\"Django\",
    \"website\":\"https://www.djangoproject.com/\",
    \"initial_git_url\":\"git://github.com/openshift/django-example.git\",
    \"cartridges\":[\"python-2.7\"],
    \"summary\":\"A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"python\",\"django\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\":{
    \"id\": \"4\",
    \"name\":\"Drupal\",
    \"website\":\"http://drupal.org/\",
    \"initial_git_url\":\"git://github.com/openshift/drupal-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"drupal\",\"wiki\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"6\",
    \"name\":\"Ruby on Rails\",
    \"website\":\"http://rubyonrails.org/\",
    \"initial_git_url\":\"git://github.com/openshift/rails-example.git\",
    \"cartridges\":[\"ruby-1.9\",\"mysql-5.1\"],
    \"summary\":\"An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.\",
    \"tags\":[\"ruby\",\"rails\",\"framework\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"8\",
    \"name\":\"WordPress\",
    \"website\":\"https://wordpress.org\",
    \"initial_git_url\":\"git://github.com/openshift/wordpress-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"wordpress\",\"blog\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }}
]"

(* Test: OpenShift_Quickstarts.lns *)
test OpenShift_Quickstarts.lns get conf =
  { "array"
    { }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { }
          { "entry" = "id"
            { "string" = "1" }
          }
          { }
          { "entry" = "name"
            { "string" = "CakePHP" }
          }
          { }
          { "entry" = "website"
            { "string" = "http://cakephp.org/" }
          }
          { }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/cakephp-example.git" }
          }
          { }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { }
          { "entry" = "summary"
            { "string" = "CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC." }
          }
          { }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "cakephp" }
              { "string" = "framework" }
            }
          }
          { }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { }
          { "entry" = "id"
            { "string" = "2" }
          }
          { }
          { "entry" = "name"
            { "string" = "Django" }
          }
          { }
          { "entry" = "website"
            { "string" = "https://www.djangoproject.com/" }
          }
          { }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/django-example.git" }
          }
          { }
          { "entry" = "cartridges"
            { "array"
              { "string" = "python-2.7" }
            }
          }
          { }
          { "entry" = "summary"
            { "string" = "A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { }
          { "entry" = "tags"
            { "array"
              { "string" = "python" }
              { "string" = "django" }
              { "string" = "framework" }
            }
          }
          { }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { }
          { "entry" = "id"
            { "string" = "4" }
          }
          { }
          { "entry" = "name"
            { "string" = "Drupal" }
          }
          { }
          { "entry" = "website"
            { "string" = "http://drupal.org/" }
          }
          { }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/drupal-example.git" }
          }
          { }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { }
          { "entry" = "summary"
            { "string" = "An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "drupal" }
              { "string" = "wiki" }
              { "string" = "framework" }
              { "string" = "instant_app" }
            }
          }
          { }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { }
          { "entry" = "id"
            { "string" = "6" }
          }
          { }
          { "entry" = "name"
            { "string" = "Ruby on Rails" }
          }
          { }
          { "entry" = "website"
            { "string" = "http://rubyonrails.org/" }
          }
          { }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/rails-example.git" }
          }
          { }
          { "entry" = "cartridges"
            { "array"
              { "string" = "ruby-1.9" }
              { "string" = "mysql-5.1" }
            }
          }
          { }
          { "entry" = "summary"
            { "string" = "An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration." }
          }
          { }
          { "entry" = "tags"
            { "array"
              { "string" = "ruby" }
              { "string" = "rails" }
              { "string" = "framework" }
            }
          }
          { }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { }
          { "entry" = "id"
            { "string" = "8" }
          }
          { }
          { "entry" = "name"
            { "string" = "WordPress" }
          }
          { }
          { "entry" = "website"
            { "string" = "http://wordpress.org" }
          }
          { }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/wordpress-example.git" }
          }
          { }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { }
          { "entry" = "summary"
            { "string" = "A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "wordpress" }
              { "string" = "blog" }
              { "string" = "framework" }
              { "string" = "instant_app" }
            }
          }
          { }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
      { }
    }
}

(* FIXME: not yet supported:
 * The manner in which the JSON utility lens currently works does not maintain
 * whitepsace as per the Augeas specification in the put direction.

test OpenShift_Quickstarts.lns put conf after set "/array/dict[5]/entry/dict/entry[3]/string" "https://wordpress.org"
  = new_conf *)

(* vim: set ts=4  expandtab  sw=4: *)

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

module Test_OpenShift_Quickstarts =

(* Variable: conf *)
let conf = "[
  {\"quickstart\": {
    \"id\": \"1\",
    \"name\":\"CakePHP\",
    \"website\":\"http://cakephp.org/\",
    \"initial_git_url\":\"git://github.com/openshift/cakephp-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC.\",
    \"tags\":[\"php\",\"cakephp\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\": {
    \"id\": \"2\",
    \"name\":\"Django\",
    \"website\":\"https://www.djangoproject.com/\",
    \"initial_git_url\":\"git://github.com/openshift/django-example.git\",
    \"cartridges\":[\"python-2.7\"],
    \"summary\":\"A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"python\",\"django\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\":{
    \"id\": \"4\",
    \"name\":\"Drupal\",
    \"website\":\"http://drupal.org/\",
    \"initial_git_url\":\"git://github.com/openshift/drupal-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"drupal\",\"wiki\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"6\",
    \"name\":\"Ruby on Rails\",
    \"website\":\"http://rubyonrails.org/\",
    \"initial_git_url\":\"git://github.com/openshift/rails-example.git\",
    \"cartridges\":[\"ruby-1.9\",\"mysql-5.1\"],
    \"summary\":\"An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.\",
    \"tags\":[\"ruby\",\"rails\",\"framework\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"8\",
    \"name\":\"WordPress\",
    \"website\":\"http://wordpress.org\",
    \"initial_git_url\":\"git://github.com/openshift/wordpress-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"wordpress\",\"blog\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }}
]"

(* Variable: new_conf *)
let new_conf = "[
  {\"quickstart\": {
    \"id\": \"1\",
    \"name\":\"CakePHP\",
    \"website\":\"http://cakephp.org/\",
    \"initial_git_url\":\"git://github.com/openshift/cakephp-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC.\",
    \"tags\":[\"php\",\"cakephp\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\": {
    \"id\": \"2\",
    \"name\":\"Django\",
    \"website\":\"https://www.djangoproject.com/\",
    \"initial_git_url\":\"git://github.com/openshift/django-example.git\",
    \"cartridges\":[\"python-2.7\"],
    \"summary\":\"A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"python\",\"django\",\"framework\"],
    \"admin_tags\":[]
  }},
  {\"quickstart\":{
    \"id\": \"4\",
    \"name\":\"Drupal\",
    \"website\":\"http://drupal.org/\",
    \"initial_git_url\":\"git://github.com/openshift/drupal-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"drupal\",\"wiki\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"6\",
    \"name\":\"Ruby on Rails\",
    \"website\":\"http://rubyonrails.org/\",
    \"initial_git_url\":\"git://github.com/openshift/rails-example.git\",
    \"cartridges\":[\"ruby-1.9\",\"mysql-5.1\"],
    \"summary\":\"An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.\",
    \"tags\":[\"ruby\",\"rails\",\"framework\"],
    \"admin_tags\":[]
  }},
 {\"quickstart\":{
    \"id\": \"8\",
    \"name\":\"WordPress\",
    \"website\":\"https://wordpress.org\",
    \"initial_git_url\":\"git://github.com/openshift/wordpress-example.git\",
    \"cartridges\":[\"php-5.4\",\"mysql-5.1\"],
    \"summary\":\"A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS.\",
    \"tags\":[\"php\",\"wordpress\",\"blog\",\"framework\",\"instant_app\"],
    \"admin_tags\":[]
  }}
]"

(* Test: OpenShift_Quickstarts.lns *)
test OpenShift_Quickstarts.lns get conf =
  { "array"
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { "entry" = "id"
            { "string" = "1" }
          }
          { "entry" = "name"
            { "string" = "CakePHP" }
          }
          { "entry" = "website"
            { "string" = "http://cakephp.org/" }
          }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/cakephp-example.git" }
          }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { "entry" = "summary"
            { "string" = "CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC." }
          }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "cakephp" }
              { "string" = "framework" }
            }
          }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { "entry" = "id"
            { "string" = "2" }
          }
          { "entry" = "name"
            { "string" = "Django" }
          }
          { "entry" = "website"
            { "string" = "https://www.djangoproject.com/" }
          }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/django-example.git" }
          }
          { "entry" = "cartridges"
            { "array"
              { "string" = "python-2.7" }
            }
          }
          { "entry" = "summary"
            { "string" = "A high-level Python web framework that encourages rapid development and clean, pragmatic design. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { "entry" = "tags"
            { "array"
              { "string" = "python" }
              { "string" = "django" }
              { "string" = "framework" }
            }
          }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { "entry" = "id"
            { "string" = "4" }
          }
          { "entry" = "name"
            { "string" = "Drupal" }
          }
          { "entry" = "website"
            { "string" = "http://drupal.org/" }
          }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/drupal-example.git" }
          }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { "entry" = "summary"
            { "string" = "An open source content management platform written in PHP powering millions of websites and applications. It is built, used, and supported by an active and diverse community of people around the world. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "drupal" }
              { "string" = "wiki" }
              { "string" = "framework" }
              { "string" = "instant_app" }
            }
          }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { "entry" = "id"
            { "string" = "6" }
          }
          { "entry" = "name"
            { "string" = "Ruby on Rails" }
          }
          { "entry" = "website"
            { "string" = "http://rubyonrails.org/" }
          }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/rails-example.git" }
          }
          { "entry" = "cartridges"
            { "array"
              { "string" = "ruby-1.9" }
              { "string" = "mysql-5.1" }
            }
          }
          { "entry" = "summary"
            { "string" = "An open source web framework for Ruby that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration." }
          }
          { "entry" = "tags"
            { "array"
              { "string" = "ruby" }
              { "string" = "rails" }
              { "string" = "framework" }
            }
          }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
    { "dict"
      { "entry" = "quickstart"
        { "dict"
          { "entry" = "id"
            { "string" = "8" }
          }
          { "entry" = "name"
            { "string" = "WordPress" }
          }
          { "entry" = "website"
            { "string" = "http://wordpress.org" }
          }
          { "entry" = "initial_git_url"
            { "string" = "git://github.com/openshift/wordpress-example.git" }
          }
          { "entry" = "cartridges"
            { "array"
              { "string" = "php-5.4" }
              { "string" = "mysql-5.1" }
            }
          }
          { "entry" = "summary"
            { "string" = "A semantic personal publishing platform written in PHP with a MySQL back end, focusing on aesthetics, web standards, and usability. Administrator user name and password are written to $OPENSHIFT_DATA_DIR/CREDENTIALS." }
          }
          { "entry" = "tags"
            { "array"
              { "string" = "php" }
              { "string" = "wordpress" }
              { "string" = "blog" }
              { "string" = "framework" }
              { "string" = "instant_app" }
            }
          }
          { "entry" = "admin_tags"
            { "array" }
          }
        }
      }
    }
}

(* FIXME: not yet supported:
 * The manner in which the JSON utility lens currently works does not maintain
 * whitepsace as per the Augeas specification in the put direction.

test OpenShift_Quickstarts.lns put conf after set "/array/dict[5]/entry/dict/entry[3]/string" "https://wordpress.org"
  = new_conf *)

(* vim: set ts=4  expandtab  sw=4: *)