Properties: add test for empty comment

Properties: add test for empty comment

module Test_properties =
    let conf = "
#
# Test tomcat properties file
#tomcat.commented.value=1
    # config
tomcat.port = 8080
tomcat.application.name=testapp
    tomcat.application.description=my test application
property.with_underscore=works
empty.property=
empty.property.withtrailingspaces=   \n! more comments
key: value
key2:value2
key3 :value3
key4:=value4

long.description=this is a description that happens to span \
	more than one line with a combination of tabs and \
        spaces \  \nor not

# comment break

short.break = a\
 b

=empty_key
 =empty_key

cheeses

spaces only
multi  spaces
  indented spaces

\= =A
space and = equals
space with \
   multiline

escaped\:colon=value
escaped\=equals=value
escaped\ space=value
"

(* Other tests that aren't supported yet
overflow.description=\
  just wanted to indent it
*)

let lns = Properties.lns

test lns get conf =
    { } { }
    { "#comment" = "Test tomcat properties file" }
    { "#comment" = "tomcat.commented.value=1" }
    { "#comment" = "config" }
    { "tomcat.port" = "8080" }
    { "tomcat.application.name" = "testapp" }
    { "tomcat.application.description" = "my test application" }
    { "property.with_underscore" = "works" }
    { "empty.property" }
    { "empty.property.withtrailingspaces" }
    { "!comment" = "more comments" }
    { "key" = "value" }
    { "key2" = "value2" }
    { "key3" = "value3" }
    { "key4" = "=value4" }
    {}
    { "long.description" = " < multi > "
        { = "this is a description that happens to span " }
        { = "more than one line with a combination of tabs and " }
        { = "spaces " }
        { = "or not" }
    }
    {}
    { "#comment" = "comment break" }
    {}
    { "short.break" = " < multi > "
        { = "a" }
        { = "b" }
    }
    {}
    { = "empty_key" }
    { = "empty_key" }
    {}
    { "cheeses" }
    {}
    { "spaces" = "only" }
    { "multi" = "spaces" }
    { "indented" = "spaces" }
    {}
    { "\\=" = "A" }
    { "space" = "and = equals" }
    { "space" = " < multi > "
        { = "with " }
        { = "multiline" }
    }
    {}
    { "escaped\:colon" = "value" }
    { "escaped\=equals" = "value" }
    { "escaped\ space" = "value" }
test lns put conf after
    set "tomcat.port" "99";
    set "tomcat.application.host" "foo.network.com"
    = "
#
# Test tomcat properties file
#tomcat.commented.value=1
    # config
tomcat.port = 99
tomcat.application.name=testapp
    tomcat.application.description=my test application
property.with_underscore=works
empty.property=
empty.property.withtrailingspaces=   \n! more comments
key: value
key2:value2
key3 :value3
key4:=value4

long.description=this is a description that happens to span \
	more than one line with a combination of tabs and \
        spaces \  \nor not

# comment break

short.break = a\
 b

=empty_key
 =empty_key

cheeses

spaces only
multi  spaces
  indented spaces

\= =A
space and = equals
space with \
   multiline

escaped\:colon=value
escaped\=equals=value
escaped\ space=value
tomcat.application.host=foo.network.com
"

(* GH issue #19: value on new line *)
test lns get "k=\
b\
c\n" =
    { "k" = " < multi > "
      { } { = "b" } { = "c" } }

test lns get "tomcat.util.scan.DefaultJarScanner.jarsToSkip=\
bootstrap.jar,commons-daemon.jar,tomcat-juli.jar\n" =
    { "tomcat.util.scan.DefaultJarScanner.jarsToSkip" = " < multi > "
      { } { = "bootstrap.jar,commons-daemon.jar,tomcat-juli.jar" } }

module Test_properties =
    let conf = "
# Test tomcat properties file
#tomcat.commented.value=1
    # config
tomcat.port = 8080
tomcat.application.name=testapp
    tomcat.application.description=my test application
property.with_underscore=works
empty.property=
empty.property.withtrailingspaces=   \n! more comments
key: value
key2:value2
key3 :value3
key4:=value4

long.description=this is a description that happens to span \
	more than one line with a combination of tabs and \
        spaces \  \nor not

# comment break

short.break = a\
 b

=empty_key
 =empty_key

cheeses

spaces only
multi  spaces
  indented spaces

\= =A
space and = equals
space with \
   multiline

escaped\:colon=value
escaped\=equals=value
escaped\ space=value
"

(* Other tests that aren't supported yet
overflow.description=\
  just wanted to indent it
*)

let lns = Properties.lns

test lns get conf =
    { }
    { "#comment" = "Test tomcat properties file" }
    { "#comment" = "tomcat.commented.value=1" }
    { "#comment" = "config" }
    { "tomcat.port" = "8080" }
    { "tomcat.application.name" = "testapp" }
    { "tomcat.application.description" = "my test application" }
    { "property.with_underscore" = "works" }
    { "empty.property" }
    { "empty.property.withtrailingspaces" }
    { "!comment" = "more comments" }
    { "key" = "value" }
    { "key2" = "value2" }
    { "key3" = "value3" }
    { "key4" = "=value4" }
    {}
    { "long.description" = " < multi > "
        { = "this is a description that happens to span " }
        { = "more than one line with a combination of tabs and " }
        { = "spaces " }
        { = "or not" }
    }
    {}
    { "#comment" = "comment break" }
    {}
    { "short.break" = " < multi > "
        { = "a" }
        { = "b" }
    }
    {}
    { = "empty_key" }
    { = "empty_key" }
    {}
    { "cheeses" }
    {}
    { "spaces" = "only" }
    { "multi" = "spaces" }
    { "indented" = "spaces" }
    {}
    { "\\=" = "A" }
    { "space" = "and = equals" }
    { "space" = " < multi > "
        { = "with " }
        { = "multiline" }
    }
    {}
    { "escaped\:colon" = "value" }
    { "escaped\=equals" = "value" }
    { "escaped\ space" = "value" }
test lns put conf after
    set "tomcat.port" "99";
    set "tomcat.application.host" "foo.network.com"
    = "
# Test tomcat properties file
#tomcat.commented.value=1
    # config
tomcat.port = 99
tomcat.application.name=testapp
    tomcat.application.description=my test application
property.with_underscore=works
empty.property=
empty.property.withtrailingspaces=   \n! more comments
key: value
key2:value2
key3 :value3
key4:=value4

long.description=this is a description that happens to span \
	more than one line with a combination of tabs and \
        spaces \  \nor not

# comment break

short.break = a\
 b

=empty_key
 =empty_key

cheeses

spaces only
multi  spaces
  indented spaces

\= =A
space and = equals
space with \
   multiline

escaped\:colon=value
escaped\=equals=value
escaped\ space=value
tomcat.application.host=foo.network.com
"

(* GH issue #19: value on new line *)
test lns get "k=\
b\
c\n" =
    { "k" = " < multi > "
      { } { = "b" } { = "c" } }

test lns get "tomcat.util.scan.DefaultJarScanner.jarsToSkip=\
bootstrap.jar,commons-daemon.jar,tomcat-juli.jar\n" =
    { "tomcat.util.scan.DefaultJarScanner.jarsToSkip" = " < multi > "
      { } { = "bootstrap.jar,commons-daemon.jar,tomcat-juli.jar" } }


Add documentation to test_keepalived.aug

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

module Test_Keepalived =

(* Variable: conf
   A full configuration file *)
   let conf = "! This is a comment
! Configuration File for keepalived

global_defs {
   ! this is who emails will go to on alerts
   notification_email {
        admins@example.com
    fakepager@example.com
    ! add a few more email addresses here if you would like
   }
   notification_email_from admins@example.com

   smtp_server 127.0.0.1  ! I use the local machine to relay mail
   smtp_connect_timeout 30

   ! each load balancer should have a different ID
   ! this will be used in SMTP alerts, so you should make
   ! each router easily identifiable
   lvs_id LVS_EXAMPLE_01
}

vrrp_sync_group VG1 {
   group {
      inside_network  # name of vrrp_instance (below)
      outside_network # One for each moveable IP.
   }
}

vrrp_instance VI_1 {
        state MASTER
        interface eth0

	track_interface {
		eth0 # Back
		eth1 # DMZ
	}
	track_script {
		check_apache2    # weight = +2 si ok, 0 si nok
	}
	garp_master_delay 5
	priority 50
	advert_int 2
	authentication {
		auth_type PASS
		auth_pass mypass
	}
	virtual_ipaddress {
		10.234.66.146/32 dev eth0
	}

        lvs_sync_daemon_interface eth0
	ha_suspend

       notify_master   \"/svr/scripts/notify_master.sh\"
       notify_backup   \"/svr/scripts/notify_backup.sh\"
       notify_fault    \"/svr/scripts/notify_fault.sh\"

    ! each virtual router id must be unique per instance name!
        virtual_router_id 51

    ! MASTER and BACKUP state are determined by the priority
    ! even if you specify MASTER as the state, the state will
    ! be voted on by priority (so if your state is MASTER but your
    ! priority is lower than the router with BACKUP, you will lose
    ! the MASTER state)
    ! I make it a habit to set priorities at least 50 points apart
    ! note that a lower number is lesser priority - lower gets less vote
        priority 150

    ! how often should we vote, in seconds?
        advert_int 1

    ! send an alert when this instance changes state from MASTER to BACKUP
        smtp_alert

    ! this authentication is for syncing between failover servers
    ! keepalived supports PASS, which is simple password
    ! authentication
    ! or AH, which is the IPSec authentication header.
    ! I don't use AH
    ! yet as many people have reported problems with it
        authentication {
                auth_type PASS
                auth_pass example
        }

    ! these are the IP addresses that keepalived will setup on this
    ! machine. Later in the config we will specify which real
        ! servers  are behind these IPs
    ! without this block, keepalived will not setup and takedown the
    ! any IP addresses

        virtual_ipaddress {
                192.168.1.11
                10.234.66.146/32 dev vlan933 # parse it well
        ! and more if you want them
        }
}

virtual_server 192.168.1.11 22 {
    delay_loop 6

    ! use round-robin as a load balancing algorithm
    lb_algo rr

    ! we are doing NAT
    lb_kind NAT
    nat_mask 255.255.255.0

    protocol TCP

    ! there can be as many real_server blocks as you need

    real_server 10.20.40.10 22 {

    ! if we used weighted round-robin or a similar lb algo,
    ! we include the weight of this server

        weight 1

    ! here is a health checker for this server.
    ! we could use a custom script here (see the keepalived docs)
    ! but we will just make sure we can do a vanilla tcp connect()
    ! on port 22
    ! if it fails, we will pull this realserver out of the pool
    ! and send email about the removal
        TCP_CHECK {
                connect_timeout 3
        connect_port 22
        }
    }
}

virtual_server_group DNS_1 {
    192.168.0.1 22
    10.234.55.22-25 36
    10.45.58.59/32 27
}

vrrp_script chk_apache2 {       # Requires keepalived-1.1.13
script \"killall -0 apache2\"   # faster
interval 2                      # check every 2 seconds
weight 2                        # add 2 points of prio if OK
}

! that's all
"

(* Test: Keepalived.lns
   Test the full <conf> *)
   test Keepalived.lns get conf =
     { "#comment" = "This is a comment" }
     { "#comment" = "Configuration File for keepalived" }
     {}
     { "global_defs"
       { }
       { "#comment" = "this is who emails will go to on alerts" }
       { "notification_email"
            { }
            { "email" = "admins@example.com" }
            { "email" = "fakepager@example.com" }
            { "#comment" = "add a few more email addresses here if you would like" } }
       { "notification_email_from" = "admins@example.com" }
       { }
       { "smtp_server" = "127.0.0.1"
         { "#comment" = "I use the local machine to relay mail" } }
       { "smtp_connect_timeout" = "30" }
       {}
       { "#comment" = "each load balancer should have a different ID" }
       { "#comment" = "this will be used in SMTP alerts, so you should make" }
       { "#comment" = "each router easily identifiable" }
       { "lvs_id" = "LVS_EXAMPLE_01" } }
     {}
     { "vrrp_sync_group" = "VG1"
       { }
       { "group"
         { }
         { "inside_network"
           { "#comment" = "name of vrrp_instance (below)" } }
         { "outside_network"
           { "#comment" = "One for each moveable IP." } } } }
     {}
     { "vrrp_instance" = "VI_1"
       { }
       { "state" = "MASTER" }
       { "interface" = "eth0" }
       { }
       { "track_interface"
         { }
         { "eth0" { "#comment" = "Back" } }
         { "eth1" { "#comment" = "DMZ" } } }
       { "track_script"
         { }
         { "check_apache2" { "#comment" = "weight = +2 si ok, 0 si nok" } } }
       { "garp_master_delay" = "5" }
       { "priority" = "50" }
       { "advert_int" = "2" }
       { "authentication"
         { }
         { "auth_type" = "PASS" }
         { "auth_pass" = "mypass" } }
       { "virtual_ipaddress"
         { }
         { "ipaddr" = "10.234.66.146"
           { "prefixlen" = "32" }
           { "dev" = "eth0" } } }
       { }
       { "lvs_sync_daemon_interface" = "eth0" }
       { "ha_suspend" }
       { }
       { "notify_master" = "\"/svr/scripts/notify_master.sh\"" }
       { "notify_backup" = "\"/svr/scripts/notify_backup.sh\"" }
       { "notify_fault" = "\"/svr/scripts/notify_fault.sh\"" }
       { }
       { "#comment" = "each virtual router id must be unique per instance name!" }
       { "virtual_router_id" = "51" }
       { }
       { "#comment" = "MASTER and BACKUP state are determined by the priority" }
       { "#comment" = "even if you specify MASTER as the state, the state will" }
       { "#comment" = "be voted on by priority (so if your state is MASTER but your" }
       { "#comment" = "priority is lower than the router with BACKUP, you will lose" }
       { "#comment" = "the MASTER state)" }
       { "#comment" = "I make it a habit to set priorities at least 50 points apart" }
       { "#comment" = "note that a lower number is lesser priority - lower gets less vote" }
       { "priority" = "150" }
       { }
       { "#comment" = "how often should we vote, in seconds?" }
       { "advert_int" = "1" }
       { }
       { "#comment" = "send an alert when this instance changes state from MASTER to BACKUP" }
       { "smtp_alert" }
       { }
       { "#comment" = "this authentication is for syncing between failover servers" }
       { "#comment" = "keepalived supports PASS, which is simple password" }
       { "#comment" = "authentication" }
       { "#comment" = "or AH, which is the IPSec authentication header." }
       { "#comment" = "I don't use AH" }
       { "#comment" = "yet as many people have reported problems with it" }
       { "authentication"
         { }
         { "auth_type" = "PASS" }
         { "auth_pass" = "example" } }
       { }
       { "#comment" = "these are the IP addresses that keepalived will setup on this" }
       { "#comment" = "machine. Later in the config we will specify which real" }
       { "#comment" = "servers  are behind these IPs" }
       { "#comment" = "without this block, keepalived will not setup and takedown the" }
       { "#comment" = "any IP addresses" }
       { }
       { "virtual_ipaddress"
         { }
         { "ipaddr" = "192.168.1.11" }
         { "ipaddr" = "10.234.66.146"
           { "prefixlen" = "32" }
           { "dev" = "vlan933" }
           { "#comment" = "parse it well" } }
         { "#comment" = "and more if you want them" } } }
       { }
       { "virtual_server"
         { "ip" = "192.168.1.11" }
         { "port" = "22" }
         { }
         { "delay_loop" = "6" }
         { }
         { "#comment" = "use round-robin as a load balancing algorithm" }
         { "lb_algo" = "rr" }
         { }
         { "#comment" = "we are doing NAT" }
         { "lb_kind" = "NAT" }
         { "nat_mask" = "255.255.255.0" }
         { }
         { "protocol" = "TCP" }
         { }
         { "#comment" = "there can be as many real_server blocks as you need" }
         { }
         { "real_server"
           { "ip" = "10.20.40.10" }
           { "port" = "22" }
           { }
           { }
           { "#comment" = "if we used weighted round-robin or a similar lb algo," }
           { "#comment" = "we include the weight of this server" }
           { }
           { "weight" = "1" }
           { }
           { "#comment" = "here is a health checker for this server." }
           { "#comment" = "we could use a custom script here (see the keepalived docs)" }
           { "#comment" = "but we will just make sure we can do a vanilla tcp connect()" }
           { "#comment" = "on port 22" }
           { "#comment" = "if it fails, we will pull this realserver out of the pool" }
           { "#comment" = "and send email about the removal" }
           { "TCP_CHECK"
             { }
             { "connect_timeout" = "3" }
             { "connect_port" = "22" } } } }
       { }
       { "virtual_server_group" = "DNS_1"
         { }
         { "vip"
	   { "ipaddr" = "192.168.0.1" }
	   { "port" = "22" } }
	 { "vip"
	   { "ipaddr" = "10.234.55.22-25" }
	   { "port" = "36" } }
	 { "vip"
	   { "ipaddr" = "10.45.58.59"
	     { "prefixlen" = "32" } }
	   { "port" = "27" } } }
       { }
       { "vrrp_script" = "chk_apache2"
         { "#comment" = "Requires keepalived-1.1.13" }
         { "script" = "\"killall -0 apache2\""
           { "#comment" = "faster" } }
         { "interval" = "2"
           { "#comment" = "check every 2 seconds" } }
         { "weight" = "2"
           { "#comment" = "add 2 points of prio if OK" } } }
       { }
       { "#comment" = "that's all" }

(* Test for keepalived lens *)

module Test_keepalived =

   let conf = "! This is a comment
! Configuration File for keepalived

global_defs {
   ! this is who emails will go to on alerts
   notification_email {
        admins@example.com
    fakepager@example.com
    ! add a few more email addresses here if you would like
   }
   notification_email_from admins@example.com

   smtp_server 127.0.0.1  ! I use the local machine to relay mail
   smtp_connect_timeout 30

   ! each load balancer should have a different ID
   ! this will be used in SMTP alerts, so you should make
   ! each router easily identifiable
   lvs_id LVS_EXAMPLE_01
}

vrrp_sync_group VG1 {
   group {
      inside_network  # name of vrrp_instance (below)
      outside_network # One for each moveable IP.
   }
}

vrrp_instance VI_1 {
        state MASTER
        interface eth0

	track_interface {
		eth0 # Back
		eth1 # DMZ
	}
	track_script {
		check_apache2    # weight = +2 si ok, 0 si nok
	}
	garp_master_delay 5
	priority 50
	advert_int 2
	authentication {
		auth_type PASS
		auth_pass mypass
	}
	virtual_ipaddress {
		10.234.66.146/32 dev eth0
	}

        lvs_sync_daemon_interface eth0
	ha_suspend

       notify_master   \"/svr/scripts/notify_master.sh\"
       notify_backup   \"/svr/scripts/notify_backup.sh\"
       notify_fault    \"/svr/scripts/notify_fault.sh\"

    ! each virtual router id must be unique per instance name!
        virtual_router_id 51

    ! MASTER and BACKUP state are determined by the priority
    ! even if you specify MASTER as the state, the state will
    ! be voted on by priority (so if your state is MASTER but your
    ! priority is lower than the router with BACKUP, you will lose
    ! the MASTER state)
    ! I make it a habit to set priorities at least 50 points apart
    ! note that a lower number is lesser priority - lower gets less vote
        priority 150

    ! how often should we vote, in seconds?
        advert_int 1

    ! send an alert when this instance changes state from MASTER to BACKUP
        smtp_alert

    ! this authentication is for syncing between failover servers
    ! keepalived supports PASS, which is simple password
    ! authentication
    ! or AH, which is the IPSec authentication header.
    ! I don't use AH
    ! yet as many people have reported problems with it
        authentication {
                auth_type PASS
                auth_pass example
        }

    ! these are the IP addresses that keepalived will setup on this
    ! machine. Later in the config we will specify which real
        ! servers  are behind these IPs
    ! without this block, keepalived will not setup and takedown the
    ! any IP addresses

        virtual_ipaddress {
                192.168.1.11
                10.234.66.146/32 dev vlan933 # parse it well
        ! and more if you want them
        }
}

virtual_server 192.168.1.11 22 {
    delay_loop 6

    ! use round-robin as a load balancing algorithm
    lb_algo rr

    ! we are doing NAT
    lb_kind NAT
    nat_mask 255.255.255.0

    protocol TCP

    ! there can be as many real_server blocks as you need

    real_server 10.20.40.10 22 {

    ! if we used weighted round-robin or a similar lb algo,
    ! we include the weight of this server

        weight 1

    ! here is a health checker for this server.
    ! we could use a custom script here (see the keepalived docs)
    ! but we will just make sure we can do a vanilla tcp connect()
    ! on port 22
    ! if it fails, we will pull this realserver out of the pool
    ! and send email about the removal
        TCP_CHECK {
                connect_timeout 3
        connect_port 22
        }
    }
}

virtual_server_group DNS_1 {
    192.168.0.1 22
    10.234.55.22-25 36
    10.45.58.59/32 27
}

vrrp_script chk_apache2 {       # Requires keepalived-1.1.13
script \"killall -0 apache2\"   # faster
interval 2                      # check every 2 seconds
weight 2                        # add 2 points of prio if OK
}

! that's all
"

   test Keepalived.lns get conf =
     { "#comment" = "This is a comment" }
     { "#comment" = "Configuration File for keepalived" }
     {}
     { "global_defs"
       { }
       { "#comment" = "this is who emails will go to on alerts" }
       { "notification_email"
            { }
            { "email" = "admins@example.com" }
            { "email" = "fakepager@example.com" }
            { "#comment" = "add a few more email addresses here if you would like" } }
       { "notification_email_from" = "admins@example.com" }
       { }
       { "smtp_server" = "127.0.0.1"
         { "#comment" = "I use the local machine to relay mail" } }
       { "smtp_connect_timeout" = "30" }
       {}
       { "#comment" = "each load balancer should have a different ID" }
       { "#comment" = "this will be used in SMTP alerts, so you should make" }
       { "#comment" = "each router easily identifiable" }
       { "lvs_id" = "LVS_EXAMPLE_01" } }
     {}
     { "vrrp_sync_group" = "VG1"
       { }
       { "group"
         { }
         { "inside_network"
           { "#comment" = "name of vrrp_instance (below)" } }
         { "outside_network"
           { "#comment" = "One for each moveable IP." } } } }
     {}
     { "vrrp_instance" = "VI_1"
       { }
       { "state" = "MASTER" }
       { "interface" = "eth0" }
       { }
       { "track_interface"
         { }
         { "eth0" { "#comment" = "Back" } }
         { "eth1" { "#comment" = "DMZ" } } }
       { "track_script"
         { }
         { "check_apache2" { "#comment" = "weight = +2 si ok, 0 si nok" } } }
       { "garp_master_delay" = "5" }
       { "priority" = "50" }
       { "advert_int" = "2" }
       { "authentication"
         { }
         { "auth_type" = "PASS" }
         { "auth_pass" = "mypass" } }
       { "virtual_ipaddress"
         { }
         { "ipaddr" = "10.234.66.146"
           { "prefixlen" = "32" }
           { "dev" = "eth0" } } }
       { }
       { "lvs_sync_daemon_interface" = "eth0" }
       { "ha_suspend" }
       { }
       { "notify_master" = "\"/svr/scripts/notify_master.sh\"" }
       { "notify_backup" = "\"/svr/scripts/notify_backup.sh\"" }
       { "notify_fault" = "\"/svr/scripts/notify_fault.sh\"" }
       { }
       { "#comment" = "each virtual router id must be unique per instance name!" }
       { "virtual_router_id" = "51" }
       { }
       { "#comment" = "MASTER and BACKUP state are determined by the priority" }
       { "#comment" = "even if you specify MASTER as the state, the state will" }
       { "#comment" = "be voted on by priority (so if your state is MASTER but your" }
       { "#comment" = "priority is lower than the router with BACKUP, you will lose" }
       { "#comment" = "the MASTER state)" }
       { "#comment" = "I make it a habit to set priorities at least 50 points apart" }
       { "#comment" = "note that a lower number is lesser priority - lower gets less vote" }
       { "priority" = "150" }
       { }
       { "#comment" = "how often should we vote, in seconds?" }
       { "advert_int" = "1" }
       { }
       { "#comment" = "send an alert when this instance changes state from MASTER to BACKUP" }
       { "smtp_alert" }
       { }
       { "#comment" = "this authentication is for syncing between failover servers" }
       { "#comment" = "keepalived supports PASS, which is simple password" }
       { "#comment" = "authentication" }
       { "#comment" = "or AH, which is the IPSec authentication header." }
       { "#comment" = "I don't use AH" }
       { "#comment" = "yet as many people have reported problems with it" }
       { "authentication"
         { }
         { "auth_type" = "PASS" }
         { "auth_pass" = "example" } }
       { }
       { "#comment" = "these are the IP addresses that keepalived will setup on this" }
       { "#comment" = "machine. Later in the config we will specify which real" }
       { "#comment" = "servers  are behind these IPs" }
       { "#comment" = "without this block, keepalived will not setup and takedown the" }
       { "#comment" = "any IP addresses" }
       { }
       { "virtual_ipaddress"
         { }
         { "ipaddr" = "192.168.1.11" }
         { "ipaddr" = "10.234.66.146"
           { "prefixlen" = "32" }
           { "dev" = "vlan933" }
           { "#comment" = "parse it well" } }
         { "#comment" = "and more if you want them" } } }
       { }
       { "virtual_server"
         { "ip" = "192.168.1.11" }
         { "port" = "22" }
         { }
         { "delay_loop" = "6" }
         { }
         { "#comment" = "use round-robin as a load balancing algorithm" }
         { "lb_algo" = "rr" }
         { }
         { "#comment" = "we are doing NAT" }
         { "lb_kind" = "NAT" }
         { "nat_mask" = "255.255.255.0" }
         { }
         { "protocol" = "TCP" }
         { }
         { "#comment" = "there can be as many real_server blocks as you need" }
         { }
         { "real_server"
           { "ip" = "10.20.40.10" }
           { "port" = "22" }
           { }
           { }
           { "#comment" = "if we used weighted round-robin or a similar lb algo," }
           { "#comment" = "we include the weight of this server" }
           { }
           { "weight" = "1" }
           { }
           { "#comment" = "here is a health checker for this server." }
           { "#comment" = "we could use a custom script here (see the keepalived docs)" }
           { "#comment" = "but we will just make sure we can do a vanilla tcp connect()" }
           { "#comment" = "on port 22" }
           { "#comment" = "if it fails, we will pull this realserver out of the pool" }
           { "#comment" = "and send email about the removal" }
           { "TCP_CHECK"
             { }
             { "connect_timeout" = "3" }
             { "connect_port" = "22" } } } }
       { }
       { "virtual_server_group" = "DNS_1"
         { }
         { "vip"
	   { "ipaddr" = "192.168.0.1" }
	   { "port" = "22" } }
	 { "vip"
	   { "ipaddr" = "10.234.55.22-25" }
	   { "port" = "36" } }
	 { "vip"
	   { "ipaddr" = "10.45.58.59"
	     { "prefixlen" = "32" } }
	   { "port" = "27" } } }
       { }
       { "vrrp_script" = "chk_apache2"
         { "#comment" = "Requires keepalived-1.1.13" }
         { "script" = "\"killall -0 apache2\""
           { "#comment" = "faster" } }
         { "interval" = "2"
           { "#comment" = "check every 2 seconds" } }
         { "weight" = "2"
           { "#comment" = "add 2 points of prio if OK" } } }
       { }
       { "#comment" = "that's all" }


Httpd: include /etc/httpd/conf/httpd.conf used on Fedora/RHEL

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=688149

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

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
let sep_spc             = Sep.space
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.eol
let empty               = Util.empty
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^ '"\t\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\n>]|\\\\"|\\\\'/
let dquot = /"([^"\\\n]|\\\\.)*"/
let squot = /'([^'\\\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 h = (sep_spc . argv arg_sec)? . sep_osp .
             dels ">" . eol . body* . indent . dels "</" in
        [ indent . dels "<" . square word h . 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

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

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
let sep_spc             = Sep.space
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.eol
let empty               = Util.empty
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^ '"\t\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\n>]|\\\\"|\\\\'/
let dquot = /"([^"\\\n]|\\\\.)*"/
let squot = /'([^'\\\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 h = (sep_spc . argv arg_sec)? . sep_osp .
             dels ">" . eol . body* . indent . dels "</" in
        [ indent . dels "<" . square word h . 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") .
             Util.stdexcl

let xfm = transform lns filter


Sshd: map comments as '#comment' nodes

Sshd: map comments as '#comment' nodes

(*
Module: Sshd
  Parses /etc/ssh/sshd_config

Author: David Lutterkort lutter@redhat.com
        Dominique Dumont dominique.dumont@hp.com

About: Reference
  sshd_config man page.
  See http://www.openbsd.org/cgi-bin/man.cgi?query=sshd_config&sektion=5

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

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get your current setup
      > print /files/etc/ssh/sshd_config
      ...

    * Set X11Forwarding to "no"
      > set /files/etc/ssh/sshd_config/X11Forwarding "no"

  More advanced usage:

    * Set a Match section
      > set /files/etc/ssh/sshd_config/Match[1]/Condition/User "foo"
      > set /files/etc/ssh/sshd_config/Match[1]/Settings/X11Forwarding "yes"

  Saving your file:

      > save

About: CAVEATS

  In sshd_config, Match blocks must be located at the end of the file.
  This means that any new "global" parameters (i.e. outside of a Match
  block) must be written before the first Match block. By default,
  Augeas will write new parameters at the end of the file.

  I.e. if you have a Match section and no ChrootDirectory parameter,
  this command:

     > set /files/etc/ssh/sshd_config/ChrootDirectory "foo"

  will be stored in a new node after the Match section and Augeas will
  refuse to save sshd_config file.

  To create a new parameter as the right place, you must first create
  a new Augeas node before the Match section:

     > ins ChrootDirectory before /files/etc/ssh/sshd_config/Match

  Then, you can set the parameter

     > set /files/etc/ssh/sshd_config/ChrootDirectory "foo"

About: Configuration files
  This lens applies to /etc/ssh/sshd_config

*)

module Sshd =
   autoload xfm

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

   let sep = Util.del_ws_spc

   let key_re = /[A-Za-z0-9]+/
         - /MACs|Match|AcceptEnv|Subsystem|(Allow|Deny)(Groups|Users)/

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

   let array_entry (k:string) =
     let value = store /[^ \t\n]+/ in
     [ key k . [ sep . seq k . value]* . eol ]

   let other_entry =
     let value = store /[^ \t\n]+([ \t]+[^ \t\n]+)*/ in
     [ key key_re . sep . value . eol ]

   let accept_env = array_entry "AcceptEnv"

   let allow_groups = array_entry "AllowGroups"
   let allow_users = array_entry "AllowUsers"
   let deny_groups = array_entry "DenyGroups"
   let deny_users = array_entry "DenyUsers"

   let subsystemvalue =
     let value = store  /[^ \t\n]+/ in
     [ key /[A-Za-z0-9]+/ . sep . value . eol ]

   let subsystem =
     let value = store  /[^ \t\n]+([ \t]+[^ \t\n]+)*/ in
     [ key "Subsystem" .  sep .  subsystemvalue ]

   let macs =
     let mac_value = store /[^, \t\n]+/ in
     [ key "MACs" . sep .
         [ seq "macs" . mac_value ] .
         ([ seq "macs" . Util.del_str "," . mac_value])* .
         eol ]

   let condition_entry =
    let value = store  /[^ \t\n]+/ in
    [ sep . key /[A-Za-z0-9]+/ . sep . value ]

   let match_cond =
     [ label "Condition" . condition_entry+ . eol ]

   let match_entry =
     ( comment | empty | (Util.indent . other_entry) )

   let match =
     [ key "Match" . match_cond
        . [ label "Settings" .  match_entry+ ]
     ]

  let lns = (comment | empty | accept_env | allow_groups | allow_users
          | deny_groups | subsystem | deny_users | macs
          | other_entry ) * . match*

  let xfm = transform lns (incl "/etc/ssh/sshd_config")

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

(*
Module: Sshd
  Parses /etc/ssh/sshd_config

Author: David Lutterkort lutter@redhat.com
        Dominique Dumont dominique.dumont@hp.com

About: Reference
  sshd_config man page.
  See http://www.openbsd.org/cgi-bin/man.cgi?query=sshd_config&sektion=5

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

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get your current setup
      > print /files/etc/ssh/sshd_config
      ...

    * Set X11Forwarding to "no"
      > set /files/etc/ssh/sshd_config/X11Forwarding "no"

  More advanced usage:

    * Set a Match section
      > set /files/etc/ssh/sshd_config/Match[1]/Condition/User "foo"
      > set /files/etc/ssh/sshd_config/Match[1]/Settings/X11Forwarding "yes"

  Saving your file:

      > save

About: CAVEATS

  In sshd_config, Match blocks must be located at the end of the file.
  This means that any new "global" parameters (i.e. outside of a Match
  block) must be written before the first Match block. By default,
  Augeas will write new parameters at the end of the file.

  I.e. if you have a Match section and no ChrootDirectory parameter,
  this command:

     > set /files/etc/ssh/sshd_config/ChrootDirectory "foo"

  will be stored in a new node after the Match section and Augeas will
  refuse to save sshd_config file.

  To create a new parameter as the right place, you must first create
  a new Augeas node before the Match section:

     > ins ChrootDirectory before /files/etc/ssh/sshd_config/Match

  Then, you can set the parameter

     > set /files/etc/ssh/sshd_config/ChrootDirectory "foo"

About: Configuration files
  This lens applies to /etc/ssh/sshd_config

*)

module Sshd =
   autoload xfm

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

   let sep = Util.del_ws_spc

   let key_re = /[A-Za-z0-9]+/
         - /MACs|Match|AcceptEnv|Subsystem|(Allow|Deny)(Groups|Users)/

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

   let array_entry (k:string) =
     let value = store /[^ \t\n]+/ in
     [ key k . [ sep . seq k . value]* . eol ]

   let other_entry =
     let value = store /[^ \t\n]+([ \t]+[^ \t\n]+)*/ in
     [ key key_re . sep . value . eol ]

   let accept_env = array_entry "AcceptEnv"

   let allow_groups = array_entry "AllowGroups"
   let allow_users = array_entry "AllowUsers"
   let deny_groups = array_entry "DenyGroups"
   let deny_users = array_entry "DenyUsers"

   let subsystemvalue =
     let value = store  /[^ \t\n]+/ in
     [ key /[A-Za-z0-9]+/ . sep . value . eol ]

   let subsystem =
     let value = store  /[^ \t\n]+([ \t]+[^ \t\n]+)*/ in
     [ key "Subsystem" .  sep .  subsystemvalue ]

   let macs =
     let mac_value = store /[^, \t\n]+/ in
     [ key "MACs" . sep .
         [ seq "macs" . mac_value ] .
         ([ seq "macs" . Util.del_str "," . mac_value])* .
         eol ]

   let condition_entry =
    let value = store  /[^ \t\n]+/ in
    [ sep . key /[A-Za-z0-9]+/ . sep . value ]

   let match_cond =
     [ label "Condition" . condition_entry+ . eol ]

   let match_entry =
     ( comment | (Util.indent . other_entry) )

   let match =
     [ key "Match" . match_cond
        . [ label "Settings" .  match_entry+ ]
     ]

  let lns = (comment | accept_env | allow_groups | allow_users
          | deny_groups | subsystem | deny_users | macs
          | other_entry ) * . match*

  let xfm = transform lns (incl "/etc/ssh/sshd_config")

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


Shellvars: include lbu.conf

Shellvars: include lbu.conf

Alpine Linux Local Backup Utility

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

(*
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/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:             *)