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

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

message:
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


old_contents:
# 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

new_contents:
# 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

subject: Remove bogus reset of 'record' counter

message: Remove bogus reset of 'record' counter


old_contents:
(* Tests for the PuppetFileserver module *)

module Test_puppetfileserver =

let fileserver = "# This a comment

[mount1]
  # Mount1 options
  path /etc/puppet/files/%h
  allow host.domain1.com
  allow *.domain2.com
  deny badhost.domain2.com
[mount2]
  allow *
  deny *.evil.example.com
  deny badhost.domain2.com
[mount3]
allow *   # Puppet #6026: same line comment
# And trailing whitespace
allow *
"

test PuppetFileserver.lns get fileserver =
	{ "#comment" = "This a comment" }
	{ }
	{ "mount1"
		{ "#comment" = "Mount1 options" }
		{ "path" = "/etc/puppet/files/%h" }
		{ "allow" = "host.domain1.com" }
		{ "allow" = "*.domain2.com" }
		{ "deny" = "badhost.domain2.com" }
	}
	{ "mount2"
		{ "allow" = "*" }
		{ "deny" = "*.evil.example.com" }
		{ "deny" = "badhost.domain2.com" }
	}
	{ "mount3"
		{ "allow" = "*"
			{ "#comment" = "Puppet #6026: same line comment" } }
		{ "#comment" = "And trailing whitespace" }
		{ "allow" = "*" }
	}

message:
Remove trailing white spaces from puppet test

Trailing white lines are valid in a puppet file, but the test is rejected when
pushing to master git repository.

remote: lenses/tests/test_puppetfileserver.aug:20: trailing whitespace.
remote: +allow *
remote: error: hook declined to update refs/heads/master
 ! [remote rejected] master -> master (hook declined)

Removing white space for now.


old_contents:
(*
Module: Test_UpdateDB
  Provides unit tests and examples for the <UpdateDB> lens.
*)
module Test_UpdateDB =

(* Test: UpdateDB.lns
     Simple get test *)
test UpdateDB.lns get "# A comment
PRUNEPATHS=\"/tmp /var/spool /media /home/.ecryptfs\"
PRUNEFS=\"NFS nfs nfs4 rpc_pipefs\"
PRUNE_BIND_MOUNTS=\"yes\"\n" =
  { "#comment" = "A comment" }
  { "PRUNEPATHS"
    { "entry" = "/tmp" }
    { "entry" = "/var/spool" }
    { "entry" = "/media" }
    { "entry" = "/home/.ecryptfs" }
  }
  { "PRUNEFS"
    { "entry" = "NFS" }
    { "entry" = "nfs" }
    { "entry" = "nfs4" }
    { "entry" = "rpc_pipefs" }
  }
  { "PRUNE_BIND_MOUNTS" = "yes" }

(* Test: UpdateDB.lns
     Adding to a list *)
test UpdateDB.lns put "PRUNEFS=\"NFS nfs nfs4 rpc_pipefs\"\n"
  after set "/PRUNEFS/entry[last()+1]" "ecryptfs" =
"PRUNEFS=\"NFS nfs nfs4 rpc_pipefs ecryptfs\"\n"

new_contents:
(*
Module: Test_UpdateDB
  Provides unit tests and examples for the <UpdateDB> lens.
*)
module Test_UpdateDB =

(* Test: UpdateDB.lns
     Simple get test *)
test UpdateDB.lns get "# A comment
PRUNEPATHS=\"/tmp /var/spool /media /home/.ecryptfs\"
PRUNEFS= \"NFS nfs nfs4 rpc_pipefs\"
PRUNE_BIND_MOUNTS = \"yes\"\n" =
  { "#comment" = "A comment" }
  { "PRUNEPATHS"
    { "entry" = "/tmp" }
    { "entry" = "/var/spool" }
    { "entry" = "/media" }
    { "entry" = "/home/.ecryptfs" }
  }
  { "PRUNEFS"
    { "entry" = "NFS" }
    { "entry" = "nfs" }
    { "entry" = "nfs4" }
    { "entry" = "rpc_pipefs" }
  }
  { "PRUNE_BIND_MOUNTS" = "yes" }

(* Test: UpdateDB.lns
     Adding to a list *)
test UpdateDB.lns put "PRUNEFS=\"NFS nfs nfs4 rpc_pipefs\"\n"
  after set "/PRUNEFS/entry[last()+1]" "ecryptfs" =
"PRUNEFS=\"NFS nfs nfs4 rpc_pipefs ecryptfs\"\n"

message: UpdateDB: Update tests to ensure spaces around = are OK


old_contents:
(*
Module: Sep
   Generic separators to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Sep =

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

new_contents:
(*
Module: Sep
   Generic separators to build lenses

Author: Raphael Pinson <raphink@gmail.com>

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

module Sep =

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

subject: Add Sep.equal, used in nrpe.aug

message: Add Sep.equal, used in nrpe.aug


old_contents:
module Fail_union_atype =
  (* This is illegal, otherwise we don't know which alternative *)
  (* to take for a tree { "a" = "?" }                           *)

  let del_str (s:string) = del s s

  let lns = [ key /a/ . store /b/ . del_str " (l)"
          | [ key /a/ . store /c/ . del_str " (r)" ]

  (* To make this a passing test, make sure that this also works: *)
  (* test lns put "ac (r)" after set "a" "b" = "ab (l)"           *)

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

new_contents:
module Fail_union_atype =
  (* This is illegal, otherwise we don't know which alternative *)
  (* to take for a tree { "a" = "?" }                           *)

  let del_str (s:string) = del s s

  let lns = [ key /a/ . store /b/ . del_str " (l)" ]
          | [ key /a/ . store /c/ . del_str " (r)" ]

  (* To make this a passing test, make sure that this also works: *)
  (* test lns put "ac (r)" after set "a" "b" = "ab (l)"           *)

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

subject: Fix syntax error in test

message: Fix syntax error in test


old_contents:
module Pass_subtree_growth =

  (* Make sure that a subtree that is not the lowest one does indeed *)
  (* grow the tree, instead of just setting the label of an enclosed *)
  (* subtree. This is only a problem if the enclosed subtree does    *)
  (* not have a label                                                *)

  let lns = [ label "outer" . [ store /a/ ] ]

  (* The improper result is { "outer" = "a" } *)
  test lns get "a" = { "outer" { = "a" } }

  test lns put "a" after set "outer" "b" = "b"

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

new_contents:
module Pass_subtree_growth =

  (* Make sure that a subtree that is not the lowest one does indeed *)
  (* grow the tree, instead of just setting the label of an enclosed *)
  (* subtree. This is only a problem if the enclosed subtree does    *)
  (* not have a label                                                *)

  let lns = [ label "outer" . [ store /a/ ] ]

  (* The improper result is { "outer" = "a" } *)
  test lns get "a" = { "outer" { = "a" } }

  (* This produces a tree { "outer" = "b" { = "a" } }         *)
  (* but the value for "outer" is never used in put           *)
  (* (That should probably be flagged as an error separately) *)
  test lns put "a" after set "outer" "b" = "a"

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

subject: Fix put test - it was checking for hte wrong result


new_contents:
(*
Module: NagiosConfig
  Parses /etc/{nagios{3,},icinga/*.cfg

Authors: Sebastien Aperghis-Tramoni <sebastien@aperghis.net>
         Raphaël Pinson <raphink@gmail.com>

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

About: Lens Usage
  To be documented

About: Configuration files
  This lens applies to /etc/{nagios{3,},icinga/*.cfg. See <filter>.
*)

module NagiosCfg =
autoload xfm

(************************************************************************
 * Group: Utility variables/functions
 ************************************************************************)
(* View: param_def
    define a field *)
let param_def =
     let space_in  = /[^ \t\n][^\n=]*[^ \t\n]|[^ \t\n]/
  in key /[A-Za-z0-9_]+/
   . Sep.space_equal
   . store space_in

(* View: macro_def
    Macro line, as used in resource.cfg *)
let macro_def =
     let macro = /\$[A-Za-z0-9]+\$/
       in let macro_decl = Rx.word | Rx.fspath
     in key macro . Sep.space_equal . store macro_decl

(************************************************************************
 * Group: Entries
 ************************************************************************)
(* View: param
    Params can have sub params *)
let param =
     [ Util.indent . param_def
     . [ Sep.space . param_def ]*
     . Util.eol ]

(* View: macro *)
let macro = [ Util.indent . macro_def . Util.eol ]

(************************************************************************
 * Group: Lens
 ************************************************************************)
(* View: entry
    Define the accepted entries, such as param for regular configuration
    files, and macro for resources.cfg .*)
let entry = param
	  | macro

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

(* View: filter *)
let filter = incl "/etc/nagios3/*.cfg"
           . incl "/etc/nagios/*.cfg"
	   . incl "/etc/icinga/*.cfg"
	   . excl "/etc/nagios3/commands.cfg"
	   . excl "/etc/nagios/commands.cfg"
	   . excl "/etc/nagios/nrpe.cfg"
	   . incl "/etc/icinga/commands.cfg"

let xfm = transform lns filter

subject: Add more recent nagios lens

message: Add more recent nagios lens


new_contents:
module Test_radicale =

   let conf = "
[server]

[encoding]

[well-known]

[auth]

[git]

[rights]

[storage]

[logging]

[headers]

"

   test Radicale.lns get conf =
      {}
      { "server"
         {} }
      { "encoding"
         {} }
      { "well-known"
         {} }
      { "auth"
         {} }
      { "git"
         {} }
      { "rights"
         {} }
      { "storage"
         {} }
      { "logging"
         {} }
      { "headers"
         {} }

    test Radicale.lns put conf after
       set "server/hosts" "127.0.0.1:5232, [::1]:5232";
       set "server/base_prefix" "/radicale/";
       set "well-known/caldav" "/radicale/%(user)s/caldav/";
       set "well-known/cardav" "/radicale/%(user)s/carddav/";
       set "auth/type" "remote_user";
       set "rights/type" "owner_only"
    = "
[server]

hosts=127.0.0.1:5232, [::1]:5232
base_prefix=/radicale/
[encoding]

[well-known]

caldav=/radicale/%(user)s/caldav/
cardav=/radicale/%(user)s/carddav/
[auth]

type=remote_user
[git]

[rights]

type=owner_only
[storage]

[logging]

[headers]

"

subject: Add test for radicale lens

message: Add test for radicale lens


new_contents:
module EPSG =

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

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

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

subject: Add basic EPSG augeas lens

message: Add basic EPSG augeas lens

repos: camptocamp/puppet-proj


old_contents:
(* Parsing /etc/fstab *)

module Fstab =
  autoload xfm

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

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

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

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

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

  let lns = ( empty | comment | record ) *

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

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

new_contents:
(* Parsing /etc/fstab *)

module Fstab =
  autoload xfm

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

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

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

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

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

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

  let xfm = transform lns filter

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

subject: Add /etc/mtab to known files

message: Add /etc/mtab to known files


old_contents:
# Parsing /etc/inittab

map
  grammar pam
  # We really need to be able to exclude some files, like
  # backup files and .rpmsave files
  include '/etc/pam.d/*' '/system/config/pam' $basename
end

grammar pam

  token SEP /[ \t]+/ = '\t'
  token EOL '\n'
  token CONTROL /(\[[^\]]*\]|[^ \t]+)/ = 'none'
  token POUND_TO_EOL /#.*\n/ = '# '

  file: ( comment | record ) *

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

  record: [ seq 'record' .
            [ label 'type' . store ... ] .
            SEP .
            [ label 'control' . store CONTROL] .
            SEP .
            [ label 'module' . store ... ] .
            ( [ SEP . label 'opts' . store ... ] )? .
            EOL
          ]
end

new_contents:
# Parsing /etc/inittab

map
  grammar pam
  # We really need to be able to exclude some files, like
  # backup files and .rpmsave files
  include '/etc/pam.d/*' '/system/config/pam' $basename
end

grammar pam

  token SEP /[ \t]+/ = '\t'
  token EOL '\n'
  token CONTROL /(\[[^\]]*\]|[^ \t]+)/ = 'none'
  token POUND_TO_EOL /#.*\n/ = '# '

  file: ( comment | record ) *

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

  record: [ seq 'record' .
            [ label 'type' . store ... ] .
            SEP .
            [ label 'control' . store CONTROL] .
            SEP .
            [ label 'module' . store ... ] .
            ( [ SEP . label 'opts' . store ... ] )? .
            EOL
          ]
end

subject: Store comments in subtrees so they are restored properly


new_contents:
(* Test let expressions *)
module Pass_let_exp =

  (* This definition is insanely roundabout; it's written that way *)
  (* since we want to exercise LET expressions                     *)
  let lns =
    let lbl = "a" in
    let spc = " " in
    let del_spaces (s:string) = del spc+ s in
    let del_str (s:string) = del s s in
    let store_delim (ldelim:string)
                    (rdelim:string) (val:regexp) =
      del_str ldelim . store val . del_str rdelim in
    [ label lbl . del_spaces " " . store_delim "(" ")" /[a-z]+/ ]

  test lns get "  (abc)" = { "a" = "abc" }

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

subject: Check that let expressions work

message: Check that let expressions work