summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--HACKING14
-rw-r--r--LICENSE6
-rw-r--r--README6
-rw-r--r--TODO1
-rw-r--r--cardinality.rng.expected54
-rw-r--r--cardinality.rng.orig (renamed from cardinality.rng)0
-rw-r--r--cardinality.rng.trang52
-rw-r--r--conv08.rng.expected5
-rw-r--r--conv08.rng.orig (renamed from conv08.rng)0
-rw-r--r--conv08.rng.trang7
-rw-r--r--corosync.rnc684
-rw-r--r--corosync.rng.expected958
-rw-r--r--corosync.rng.trang907
-rw-r--r--corosync.trang.rng907
-rw-r--r--lex.py1454
l---------patron.rng.expected1
-rw-r--r--patron.rng.orig (renamed from patron.rng)0
l---------patron.rng.trang1
-rw-r--r--regextest.rng.expected27
l---------regextest.rng.trang1
-rw-r--r--res08.rng.expected8
-rw-r--r--res08.rng.orig (renamed from res08.rng)0
l---------res08.rng.trang1
-rw-r--r--rnc_tokenize.py294
-rwxr-xr-xrnctree.py483
-rwxr-xr-xtests.sh52
-rw-r--r--unused/conv09.rng (renamed from conv09.rng)0
-rw-r--r--unused/curious.dtd (renamed from curious.dtd)0
-rw-r--r--unused/curious.xml (renamed from curious.xml)0
-rw-r--r--unused/patron-2.rng (renamed from patron-2.rng)0
-rw-r--r--unused/patron-i1.xml (renamed from patron-i1.xml)0
-rw-r--r--unused/patron-i2.xml (renamed from patron-i2.xml)0
-rw-r--r--unused/patron-i3.xml (renamed from patron-i3.xml)0
-rw-r--r--unused/patron-v1.xml (renamed from patron-v1.xml)0
-rw-r--r--unused/patron-v2.xml (renamed from patron-v2.xml)0
-rw-r--r--unused/res09.rng (renamed from res09.rng)0
-rw-r--r--unused/spectest.xml (renamed from spectest.xml)0
-rw-r--r--unused/split.xsl (renamed from split.xsl)0
-rw-r--r--unused/testSuite.rng (renamed from testSuite.rng)0
-rw-r--r--unused/unused-counterparts/patron.xsd (renamed from patron.xsd)0
-rw-r--r--unused/unused-counterparts/regextest.xml (renamed from regextest.xml)0
-rw-r--r--unused/unused-counterparts/xsdtest.xml (renamed from xsdtest.xml)0
-rw-r--r--unused/unused-counterparts/xsdtest.xsl (renamed from xsdtest.xsl)0
l---------xsdtest.rng.expected1
-rw-r--r--xsdtest.rng.orig (renamed from xsdtest.rng)0
l---------xsdtest.rng.trang1
47 files changed, 5226 insertions, 702 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..81a36de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.pyc
+*.pyo
+*.testrun
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..40dbf4c
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,14 @@
+Please, any time the script does not work for your use-case and you want
+to fix it and voluntarily provide a patch to be integrated, follow these
+guidelines:
+
+1/ some kind of minimal test case should be added to the test suite
+
+2/ no test from the original test suite should be broken after
+ applying the patch unless there is a pretty good reason to change
+ something and then, new expected results have to be provided
+
+3/ try to comply with style (and use checkers like pep8, pyflakes)
+
+
+Refactoring effort is welcome as well (please respect 2/ and 3/).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..085fe74
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,6 @@
+Original project released to the Public Domain by David Mertz.
+
+Extended under revised BSD license as stated in the modified files.
+
+Bundled is lex.py from PLY project by David M. Beazley (Dabeaz LLC),
+license of which (also revised BSD) is included in the respective file.
diff --git a/README b/README
index 858ea6e..b45afe3 100644
--- a/README
+++ b/README
@@ -3,8 +3,8 @@ Example files and programming tools for working with RELAX NG
Some of these files are part of James Clark's test suite (see
clark.html for some details).
-All of the examples and scripts (unless indicated otherwise within
-the file) are released to the Public Domain.
+All of the examples and scripts, unless indicated otherwise within
+the file, are released to the Public Domain.
+++
@@ -15,3 +15,5 @@ and place them in a working directory (or in your $PYTHONPATH):
rnctree.py
rnc_tokenize.py
lex.py
+
+Update: ... or clone the repo or wait for the package
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..09e0cc1
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+- packagify (with upfront notification of PyPI maintainer)
diff --git a/cardinality.rng.expected b/cardinality.rng.expected
new file mode 100644
index 0000000..f49540b
--- /dev/null
+++ b/cardinality.rng.expected
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0">
+ <start>
+ <element name="bar">
+ <group>
+ <ref name="fivefoo"/>
+ <ref name="upto25foo"/>
+ </group>
+ </element>
+ </start>
+ <define name="fivefoo">
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ </define>
+ <define name="maybefoo">
+ <optional>
+ <element name="foo">
+ <empty/>
+ </element>
+ </optional>
+ </define>
+ <define name="upto25foo">
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ </define>
+</grammar>
diff --git a/cardinality.rng b/cardinality.rng.orig
index 765d3e1..765d3e1 100644
--- a/cardinality.rng
+++ b/cardinality.rng.orig
diff --git a/cardinality.rng.trang b/cardinality.rng.trang
new file mode 100644
index 0000000..5bd72ca
--- /dev/null
+++ b/cardinality.rng.trang
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0">
+ <start>
+ <element name="bar">
+ <ref name="fivefoo"/>
+ <ref name="upto25foo"/>
+ </element>
+ </start>
+ <define name="fivefoo">
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ <element name="foo">
+ <empty/>
+ </element>
+ </define>
+ <define name="maybefoo">
+ <optional>
+ <element name="foo">
+ <empty/>
+ </element>
+ </optional>
+ </define>
+ <define name="upto25foo">
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <optional>
+ <ref name="fivefoo"/>
+ </optional>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ <ref name="maybefoo"/>
+ </define>
+</grammar>
diff --git a/conv08.rng.expected b/conv08.rng.expected
new file mode 100644
index 0000000..7fcd25c
--- /dev/null
+++ b/conv08.rng.expected
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<include href="res08.rnc" xmlns="http://relaxng.org/ns/structure/1.0"/>
+<define name="foo.body">
+ <text/>
+</define>
diff --git a/conv08.rng b/conv08.rng.orig
index eeb207c..eeb207c 100644
--- a/conv08.rng
+++ b/conv08.rng.orig
diff --git a/conv08.rng.trang b/conv08.rng.trang
new file mode 100644
index 0000000..5a50bc2
--- /dev/null
+++ b/conv08.rng.trang
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0">
+ <include href="res08.expected"/>
+ <define name="foo.body">
+ <text/>
+ </define>
+</grammar>
diff --git a/corosync.rnc b/corosync.rnc
new file mode 100644
index 0000000..7e90d2c
--- /dev/null
+++ b/corosync.rnc
@@ -0,0 +1,684 @@
+# Copyright 2013 Red Hat, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+#
+# style guide (RELAX NG Compact only):
+# * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence
+# separator, AsciiDoc formatting is used within them
+# * sort everything you can to ease the lookup
+
+namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
+namespace a4doc = "http://people.redhat.com/jpokorny/ns/a4doc"
+
+start = corosync
+
+corosync =
+ element corosync {
+ (logging?
+ & nodelist?
+ & quorum?
+ & totem
+ & uidgid?)
+ }
+
+macro.logging_attributes =
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies whether debug output is
+ ## logged for this particular logger. Also can contain value trace, what
+ ## is highest level of debug informations.
+ [ a:defaultValue = "off" ]
+ attribute debug {"off"|"on"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the logfile level for this particular subsystem. Ignored
+ ## if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug'
+ ## (same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.
+ [ a:defaultValue = "info" ]
+ attribute logfile_priority {"alert"
+ |"crit"
+ |"debug"
+ |"emerg"
+ |"err"
+ |"info"
+ |"notice"
+ |"warning"
+ }?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the syslog facility type
+ ## that will be used for any messages sent to syslog. Options are
+ ## 'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5',
+ ## 'local6' and 'local7'.
+ [ a:defaultValue = "daemon" ]
+ attribute syslog_facility {"daemon"
+ |"local0"
+ |"local1"
+ |"local2"
+ |"local3"
+ |"local4"
+ |"local5"
+ |"local6"
+ |"local7"
+ }?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the syslog level for this
+ ## particular subsystem. Ignored if *debug* is 'on'. Possible values are:
+ ## 'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err',
+ ## 'info', 'notice' and 'warning'.
+ [ a:defaultValue = "info" ]
+ attribute syslog_priority {"alert"
+ |"crit"
+ |"debug"
+ |"emerg"
+ |"err"
+ |"info"
+ |"notice"
+ |"warning"
+ }?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the destination
+ ## of logging output.
+ ##
+ ## Please note, if you are using *to_logfile* and want to rotate the file,
+ ## use `logrotate(8)` with the option `copytruncate`, e.g.
+ ##
+ ## ----
+ ## /var/log/corosync.log {
+ ## missingok
+ ## compress
+ ## notifempty
+ ## daily
+ ## rotate 7
+ ## copytruncate
+ ## }
+ ## ----
+ [ a:defaultValue = "no" ]
+ attribute to_logfile {"no"|"yes"}?,
+
+ # CFG: corosync.conf
+ ## This specifies the destination
+ ## of logging output.
+ [ a:defaultValue = "yes" ]
+ attribute to_stderr {"no"|"yes"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the destination
+ ## of logging output.
+ [ a:defaultValue = "yes" ]
+ attribute to_syslog {"no"|"yes"}?
+
+logging =
+ element logging {
+ # CFG: corosync.conf
+ ## This specifies that a timestamp is placed
+ ## on all log messages.
+ [ a:defaultValue = "off" ]
+ attribute timestamp {"off"|"on"}?,
+
+ # CFG: corosync.conf
+ ## This specifies that file and line should
+ ## be printed.
+ [ a:defaultValue = "off" ]
+ attribute fileline {"off"|"on"}?,
+
+ # CFG: corosync.conf
+ ## This specifies that the code function name
+ ## should be printed.
+ [ a:defaultValue = "off" ]
+ attribute function_name {"off"|"on"}?,
+
+ macro.logging_attributes,
+ logger_subsys*
+ }
+
+logger_subsys =
+ element logger_subsys {
+ macro.logging_attributes,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the subsystem identity
+ ## (name) for which logging is specified. This is the name used by
+ ## a service in the `log_init` call. E.g., 'CPG'.
+ ## This option is required.
+ attribute subsys {text}
+ }
+
+nodelist =
+ element nodelist {
+ node?
+ }
+
+node =
+ element node {
+ # XXX: implied check
+ # CFG: corosync.conf, cluster.conf
+ ## This configuration option is optional when
+ ## using IPv4 and required when using IPv6. This is a 32 bit value
+ ## specifying the node identifier delivered to the cluster membership
+ ## service. If this is not specified with IPv4, the node id will be
+ ## determined from the 32 bit IP address the system to which the system
+ ## is bound with ring identifier of 0. The node identifier value of zero
+ ## is reserved and should not be used.
+ attribute noid {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies IP address of one of the nodes for particular ring
+ ## as denoted by its number (instead 0, there can be higher numbers).
+ attribute ring0_addr {text},
+ attribute ring1_addr {text}?,
+ attribute ring2_addr {text}?,
+ attribute ring3_addr {text}?,
+ attribute ring4_addr {text}?,
+ attribute ring5_addr {text}?,
+ attribute ring6_addr {text}?,
+ attribute ring7_addr {text}?,
+ attribute ring8_addr {text}?,
+ attribute ring9_addr {text}?
+ # NOTE: Augeas lens for corosync.conf counts on X = 0..9 only
+ }
+
+quorum =
+ element quorum {
+ # CFG: corosync.conf
+ ## This enables Downscale feature
+ ## (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute allow_downscale {"0"|"1"}?,
+
+ # CFG: corosync.conf
+ ## This enables Auto Tie Breaker feature
+ ## (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute auto_tie_breaker {"0"|"1"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the number of expected votes, overriding the number
+ ## implied by the number of *node* items within *nodes*.
+ attribute expected_votes {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This enables Last Man Standing feature
+ ## (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute last_man_standing {"0"|"1"}?,
+
+ # CFG: corosync.conf
+ ## This specifies the tunable for Last Man
+ ## Standing feature (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute last_man_standing_window {xsd:nonNegativeInteger}?,
+
+ # CFG: corosync.conf
+ ## This specifies the quorum algorithm to use.
+ ## As of now, only 'corosync_votequorum' is supported.
+ attribute provider {"corosync_votequorum"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This enables two node cluster operations
+ ## (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute two_node {"0"|"1"}?,
+
+ # CFG: corosync.conf
+ ## This enables Wait For All feature
+ ## (see `votequorum(5)`).
+ [ a:defaultValue = "0" ]
+ attribute wait_for_all {"0"|"1"}?
+ }
+
+totem =
+ element totem {
+ # CFG: corosync.conf
+ ## This configuration option is optional and
+ ## is only relevant when no *nodeid* is specified. Some corosync clients
+ ## require a signed 32 bit nodeid that is greater than zero however by
+ ## default corosync uses all 32 bits of the IPv4 address space when
+ ## generating a nodeid. Set this option to 'yes' to force the high bit
+ ## to be zero and therefor ensure the nodeid is a positive signed 32 bit
+ ## integer.
+ [ a:defaultValue = "no"
+ a4doc:discretion-hint =
+ "The clusters behavior is undefined if this option is enabled"
+ ~ " on only a subset of the cluster (for example during a rolling"
+ ~ " upgrade)." ]
+ attribute clear_node_high_bit {"no"|"yes"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ # NOTE: not a direct mapping in cluster.conf (top-level tag instead)
+ ## This specifies the name of cluster and it's
+ ## used for automatic generating of multicast address.
+ attribute cluster_name {text}?,
+
+ # XXX: implied check
+ # CFG: corosync.conf, cluster.conf
+ ## This timeout specifies in milliseconds how
+ ## long to wait for consensus to be achieved before starting a new round
+ ## of membership configuration. The minimum value for *consensus* must be
+ ## 1.2 * *token*.
+ ##
+ ## This value will be automatically calculated at 1.2 * *token* if
+ ## the user doesn't specify a *consensus* value.
+ ##
+ ## For two node clusters, a *consensus* larger then the *join* timeout but
+ ## less then *token* is safe. For three node or larger clusters,
+ ## *consensus* should be larger then token. There is an increasing risk
+ ## of odd membership changes, which still guarantee virtual synchrony,
+ ## as node count grows if *consensus* is less than *token*.
+ [ a:defaultValue = "1200" ]
+ attribute consensus {xsd:unsignedInt}?,
+
+ # XXX: missing nss?
+ # CFG: corosync.conf
+ ## This specifies which cipher should be used
+ ## to encrypt all messages. Valid values are 'none' (no encryption),
+ ## 'aes256', 'aes192', 'aes128' and '3des'.
+ [ a:defaultValue = "aes256" ]
+ attribute crypto_cipher {"3des"|"aes128"|"aes192"|"aes256"|"none"}?,
+
+ # CFG: undocumented
+ attribute crypto_compat {"2.0"|"2.2"}?,
+
+ # CFG: corosync.conf
+ ## This specifies which HMAC authentication
+ ## should be used to authenticate all messages. Valid values are 'none'
+ ## (no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.
+ [ a:defaultValue = "sha1" ]
+ attribute crypto_hash {"none"|"md5"|"sha1"|"sha256"|"sha384"|"sha512"}?,
+
+ # CFG: undocumented
+ attribute crypto_type {"3des"|"aes128"|"aes192"|"aes256"|"nss"}?,
+
+ # CFG: corosync.conf
+ ## This timeout specifies in milliseconds how
+ ## long to wait before checking that a network interface is back up after
+ ## it has been downed.
+ [ a:defaultValue = "1000" ]
+ attribute downcheck {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This constant specifies how many rotations
+ ## of the token without receiving any of the messages when messages should
+ ## be received may occur before a new configuration is formed.
+ [ a:defaultValue = "2500" ]
+ attribute fail_recv_const {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## Configures the optional HeartBeating
+ ## mechanism for faster failure detection. Keep in mind that engaging this
+ ## mechanism in lossy networks could cause faulty loss declaration as
+ ## the mechanism relies on the network for heartbeating.
+ ##
+ ## So as a rule of thumb use this mechanism if you require improved
+ ## failure in low to medium utilized networks.
+ ##
+ ## This constant specifies the number of heartbeat failures the system
+ ## should tolerate before declaring heartbeat failure, e.g., 3.
+ ## Also if this value is not set or is 0 then the heartbeat mechanism is
+ ## not engaged in the system and token rotation is the method of failure
+ ## detection. Zero disables the mechanism.
+ [ a:defaultValue = "0" ]
+ attribute heartbeat_failures_allowed {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This timeout specifies in milliseconds
+ ## how long the token should be held by the representative when
+ ## the protocol is under low utilization.
+ [ a:defaultValue = "180"
+ a4doc:danger-hint =
+ "It is not recommended to override this value without guidance"
+ ~ " from the corosync community." ]
+ attribute hold {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This timeout specifies in milliseconds how
+ ## long to wait for join messages in the membership protocol.
+ [ a:defaultValue = "50" ]
+ attribute join {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This constant specifies the maximum number
+ ## of messages that may be sent by one processor on receipt of the token.
+ ## The *max_messages* parameter is limited to 256000 / *netmtu* to prevent
+ ## overflow of the kernel transmit buffers.
+ [ a:defaultValue = "17" ]
+ attribute max_messages {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This constant specifies in milliseconds
+ ## the approximate delay that your network takes to transport one packet
+ ## from one machine to another. This value is to be set by system engineers
+ ## and please don't change it if not sure as this effects the failure
+ ## detection mechanism using heartbeat.
+ [ a:defaultValue = "50" ]
+ attribute max_network_delay {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This timeout specifies in milliseconds how
+ ## long to wait before checking for a partition when no multicast traffic
+ ## is being sent. If multicast traffic is being sent, the merge detection
+ ## happens automatically as a function of the protocol.
+ [ a:defaultValue = "200" ]
+ attribute merge {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This constant defines the maximum number
+ ## of times on receipt of a token a message is checked for retransmission
+ ## before a retransmission occurs. This parameter is useful to modify for
+ ## switches that delay multicast packets compared to unicast packets.
+ ## The default setting works well for nearly all modern switches.
+ [ a:defaultValue = "5" ]
+ attribute miss_count_const {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the network maximum transmit
+ ## unit. To set this value beyond 1500, the regular frame MTU, requires
+ ## ethernet devices that support large, or also called jumbo, frames.
+ ## If any device in the network doesn't support large frames, the protocol
+ ## will not operate properly. The hosts must also have their mtu size set
+ ## from 1500 to whatever frame size is specified here.
+ ##
+ ## Please note while some NICs or switches
+ ## claim large frame support, they support 9000 MTU as the maximum frame
+ ## size including the IP header. Setting the *netmtu* and host MTUs to 9000
+ ## will cause totem to use the full 9000 bytes of the frame. Then Linux
+ ## will add a 18 byte header moving the full frame size to 9018.
+ ## As a result some hardware will not operate properly with this size
+ ## of data. A *netmtu* of 8982 seems to work for the few large frame devices
+ ## that have been tested. Some manufacturers claim large frame support
+ ## when in fact they support frame sizes of 4500 bytes.
+ ##
+ ## When sending multicast traffic, if the network frequently reconfigures,
+ ## chances are that some device in the network doesn't support large frames.
+ ##
+ ## Choose hardware carefully if intending to use large frame support.
+ [ a:defaultValue = "1500" ]
+ attribute netmtu {xsd:unsignedInt}?,
+
+ # CFG: undocumented
+ attribute nodeid {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This specifies the time in milliseconds
+ ## to check if the failed ring can be auto-recovered.
+ [ a:defaultValue = "1000" ]
+ attribute rrp_autorecovery_check_timeout {xsd:unsignedInt}?,
+
+ # XXX: implied check: active,passive -> count(interface) <= 2
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the mode of redundant ring,
+ ## which may be 'none', 'active', or 'passive'. Active replication offers
+ ## none`, active, or passive. Active replication offers
+ ## slightly lower latency from transmit to delivery in faulty network
+ ## environments but with less performance. Passive replication may nearly
+ ## double the speed of the totem protocol if the protocol doesn't become
+ ## CPU bound. The final option is none, in which case only one network
+ ## interface will be used to operate the totem protocol.
+ ##
+ ## If only one *interface* directive is specified, 'none' is automatically
+ ## chosen. If multiple *interface* directives are specified, only 'active'
+ ## or 'passive' may be chosen.
+ ##
+ ## The maximum number of *interface* directives that is allowed for either
+ ## mode ('active' or 'passive') is 2.
+ attribute rrp_mode {"active"|"none"|"passive"}?,
+
+ # CFG: corosync.conf
+ ## This specifies the number of times
+ ## a problem is detected with multicast before setting the link faulty for
+ ## passive RRP mode. This variable is unused in active RRP mode.
+ ##
+ ## The default is 10 times *rrp_problem_count_threshold*.
+ attribute rrp_problem_count_mcast_threshold {xsd:unsignedInt}?,
+
+ # XXX: implied check
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the number of times
+ ## a problem is detected with a link before setting the link faulty.
+ ## Once a link is set faulty, no more data is transmitted upon it. Also,
+ ## the problem counter is no longer decremented when the problem count
+ ## timeout expires.
+ ##
+ ## A problem is detected whenever all tokens from the proceeding
+ ## processor have not been received within the *rrp_token_expired_timeout*.
+ ## The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be
+ ## at least 50 milliseconds less then the *token* timeout, or a complete
+ ## reconfiguration may occur.
+ [ a:defaultValue = "10" ]
+ attribute rrp_problem_count_threshold {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This specifies the time in milliseconds
+ ## to wait before decrementing the problem count by 1 for a particular ring
+ ## to ensure a link is not marked faulty for transient network failures.
+ [ a:defaultValue = "2000" ]
+ attribute rrp_problem_count_timeout {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This specifies the time in milliseconds
+ ## to increment the problem counter for the redundant ring protocol after
+ ## not having received a token from all rings for a particular processor.
+ ##
+ ## This value will automatically be calculated from the *token* timeout
+ ## and *problem_count_threshold* but may be overridden.
+ [ a:defaultValue = "47"
+ a4doc:danger-hint =
+ "It is not recommended to override this value without guidance"
+ ~ " from the corosync community." ]
+ attribute rrp_token_expired_timeout {xsd:unsignedInt}?,
+
+ # XXX: implied check/migration to current items
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies that HMAC/SHA1 authentication should be used
+ ## to authenticate all messages. It further specifies that all data
+ ## should be encrypted with the nss library and aes256 encryption
+ ## algorithm to protect data from eavesdropping.
+ ##
+ ## Enabling this option adds a encryption header to every message sent
+ ## by totem which reduces total throughput. Also encryption and
+ ## authentication consume extra CPU cycles in corosync.
+ [ a:defaultValue = "on"
+ a4doc:deprecation-hint =
+ "It's recomended to use combination of *crypto_cipher* and *crypto_hash*."
+ ]
+ attribute secauth {"off"|"on"}?,
+
+ # CFG: corosync.conf
+ ## This timeout specifies in milliseconds
+ ## an upper range between 0 and *send_join* to wait before sending a join
+ ## message. For eprecationtions with less then 32 nodes, this parameter
+ ## is not necessary. For larger rings, this parameter is necessary
+ ## to ensure the NIC is not overflowed with join messages on formation of
+ ## a new ring. A reasonable value for large rings (128 nodes) would be
+ ## 80msec. Other timer values must also change if this value is changed.
+ [ a:defaultValue = "0"
+ a4doc:danger-hint =
+ "Seek advice from the corosync mailing list if trying to run"
+ ~ " larger configurations." ]
+ attribute send_join {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This constant specifies how many rotations
+ ## of the token without any multicast traffic should occur before the hold
+ ## timer is started.
+ [ a:defaultValue = "30" ]
+ attribute seqno_unchanged_const {xsd:unsignedInt}?,
+
+ # CFG: undocumented
+ attribute threads {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This timeout specifies in milliseconds
+ ## until a token loss is declared after not receiving a token. This is
+ ## the time spent detecting a failure of a processor in the current
+ ## configuration. Reforming a new configuration takes about 50
+ ## milliseconds in addition to this timeout.
+ [ a:defaultValue = "1000" ]
+ attribute token {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ ## This timeout specifies in milliseconds
+ ## after how long before receiving a token the token is retransmitted.
+ ## This will be automatically calculated if token is modified.
+ [ a:defaultValue = "238"
+ a4doc:danger-hint =
+ "It is not recommended to override this value without guidance"
+ ~ " from the corosync community." ]
+ attribute token_retransmit {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This value identifies how many token
+ ## retransmits should be attempted before forming a new configuration.
+ ## If this value is set, retransmit and hold will be automatically
+ ## calculated from *retransmits_before_loss* and token.
+ [ a:defaultValue = "4" ]
+ attribute token_retransmits_before_loss_const {xsd:unsignedInt}?,
+
+ # CFG: corosync.conf
+ [ a:defaultValue = "udp" ]
+ attribute transport {"iba"|"udp"|"udpu"}?,
+ ## This option controls the transport
+ ## mechanism used. If the interface to which corosync is binding is
+ ## an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter
+ ## may be specified. To avoid the use of multicast entirely, a unicast
+ ## transport parameter 'udpu' can be specified. This requires specifying
+ ## the list of members in *nodelist* directive, that could potentially make
+ ## up the membership before deployment.
+
+ # CFG: corosync.conf
+ ## This specifies the version of
+ ## the configuration file. Currently the only valid value for this
+ ## option is '2'.
+ attribute version {xsd:unsignedInt},
+
+ # CFG: corosync.conf
+ ## This option controls the virtual
+ ## synchrony filter type used to identify a primary component.
+ ## The preferred choice is YKD dynamic linear voting, however, for
+ ## clusters larger then 32 nodes YKD consumes alot of memory. For large
+ ## scale clusters that are created by changing the MAX_PROCESSORS_COUNT
+ ## #define in the C code totem.h file, the virtual synchrony filter 'none'
+ ## is recommended but then AMF and DLCK services (which are currently
+ ## experimental) are not safe for use.
+ [ a:defaultValue = "ykd" ]
+ attribute vsftype {"none"|"ykd"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This constant specifies the maximum number
+ ## of messages that may be sent on one token rotation. If all processors
+ ## perform equally well, this value could be large (300), which would
+ ## introduce higher latency from origination to delivery for very large
+ ## rings. To reduce latency in large rings (16+), the defaults are a safe
+ ## compromise. If 1 or more slow processor(s) are present among fast
+ ## processors, *window_size* should be no larger then 256000 / *netmtu*
+ ## to avoid overflow of the kernel receive buffers. The user is notified
+ ## of this by the display of a retransmit list in the notification logs.
+ ## There is no loss of data, but performance is reduced when these errors
+ ## occur.
+ [ a:defaultValue = "50" ]
+ attribute window_size {xsd:unsignedInt}?,
+
+ interface*
+ }
+
+interface =
+ element interface {
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the network address
+ ## the corosync executive should bind to.
+ ## *bindnetaddr* should be an IP address configured on the system, or
+ ## a network address.
+ ##
+ ## For example, if the local interface is `192.168.5.92` with netmask
+ ## `255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or
+ ## `192.168.5.0`. If the local interface is `192.168.5.92` with netmask
+ ## `255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`,
+ ## and so forth.
+ ##
+ ## This may also be an IPv6 address, in which case IPv6 networking will be
+ ## used. In this case, the exact address must be specified and there is no
+ ## automatic selection of the network interface within a specific subnet
+ ## as with IPv4.
+ ##
+ ## If IPv6 networking is used, the *nodeid* field in *nodelist* must be
+ ## specified.
+ attribute bindnetaddr {text}?,
+
+ # CFG: corosync.conf
+ ## This is optional and can be set to 'yes'. If it is set to 'yes',
+ ## the broadcast address will be used for communication. If this option
+ ## is set, *mcastaddr* should not be set.
+ [ a:defaultValue = "no" ]
+ attribute broadcast {"no"|"yes"}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This is the multicast address used
+ ## by corosync executive. The default should work for most networks, but
+ ## the network administrator should be queried about a multicast address
+ ## to use. Avoid `224.x.x.x` because this is a "config" multicast address.
+ ##
+ ## This may also be an IPv6 multicast address, in which case IPv6 networking
+ ## will be used. If IPv6 networking is used, the *nodeid* field in
+ ## *nodelist* must be specified.
+ ##
+ ## It's not needed to use this option if *cluster_name* option is used.
+ ## If both options are used, *mcastaddr* has higher priority.
+ attribute mcastaddr {text}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the UDP port number.
+ ## It is possible to use the same multicast address on a network with
+ ## the corosync services configured for different UDP ports. Please note
+ ## corosync uses two UDP ports *mcastport* (for mcast receives) and
+ ## *mcastport* - 1 (for mcast sends). If you have multiple clusters
+ ## on the same network using the same *mcastaddr* please configure
+ ## the **mcastport**s with a gap.
+ attribute mcastport {xsd:unsignedShort}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the ring number for
+ ## the interface. When using the redundant ring protocol, each interface
+ ## should specify separate ring numbers to uniquely identify to
+ ## the membership protocol which interface to use for which redundant ring.
+ ## The *ringnumber* must start at 0.
+ attribute ringnumber {xsd:unsignedByte}?,
+
+ # CFG: corosync.conf, cluster.conf
+ ## This specifies the Time To Live (TTL).
+ ## If you run your cluster on a routed network then the default of '1' will
+ ## be too small. This option provides a way to increase this up to '255'.
+ ## The valid range is '0..255'. Note that this is only valid on multicast
+ ## transport types.
+ [ a:defaultValue = "1" ]
+ attribute ttl {xsd:unsignedByte}?
+ }
+
+#uidgid =
+# element uidgid {
+# attribute uid {text}?,
+# attribute gid {text}?
+# }
diff --git a/corosync.rng.expected b/corosync.rng.expected
new file mode 100644
index 0000000..3e4ca2f
--- /dev/null
+++ b/corosync.rng.expected
@@ -0,0 +1,958 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
+ xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc">
+ <!--
+ Copyright 2013 Red Hat, Inc.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ - Neither the name of the Red Hat, Inc. nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+ style guide (RELAX NG Compact only):
+ * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence
+ separator, AsciiDoc formatting is used within them
+ * sort everything you can to ease the lookup
+ -->
+ <start>
+ <ref name="corosync"/>
+ </start>
+ <define name="corosync">
+ <element name="corosync">
+ <interleave>
+ <optional>
+ <ref name="logging"/>
+ </optional>
+ <optional>
+ <ref name="nodelist"/>
+ </optional>
+ <optional>
+ <ref name="quorum"/>
+ </optional>
+ <ref name="totem"/>
+ <optional>
+ <ref name="uidgid"/>
+ </optional>
+ </interleave>
+ </element>
+ </define>
+ <define name="macro.logging_attributes">
+ <optional>
+ <attribute name="debug" a:defaultValue="off">
+ <a:documentation>This specifies whether debug output is
+logged for this particular logger. Also can contain value trace, what
+is highest level of debug informations.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="logfile_priority" a:defaultValue="info">
+ <a:documentation>This specifies the logfile level for this particular subsystem. Ignored
+if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug'
+(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="syslog_facility" a:defaultValue="daemon">
+ <a:documentation>This specifies the syslog facility type
+that will be used for any messages sent to syslog. Options are
+'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5',
+'local6' and 'local7'.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>daemon</value>
+ <value>local0</value>
+ <value>local1</value>
+ <value>local2</value>
+ <value>local3</value>
+ <value>local4</value>
+ <value>local5</value>
+ <value>local6</value>
+ <value>local7</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="syslog_priority" a:defaultValue="info">
+ <a:documentation>This specifies the syslog level for this
+particular subsystem. Ignored if *debug* is 'on'. Possible values are:
+'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err',
+'info', 'notice' and 'warning'.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="to_logfile" a:defaultValue="no">
+ <a:documentation>This specifies the destination
+of logging output.
+
+Please note, if you are using *to_logfile* and want to rotate the file,
+use `logrotate(8)` with the option `copytruncate`, e.g.
+
+----
+/var/log/corosync.log {
+ missingok
+ compress
+ notifempty
+ daily
+ rotate 7
+ copytruncate
+}
+----</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="to_stderr" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="to_syslog" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ </define>
+ <define name="logging">
+ <element name="logging">
+ <group>
+ <optional>
+ <attribute name="timestamp" a:defaultValue="off">
+ <a:documentation>This specifies that a timestamp is placed
+on all log messages.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="fileline" a:defaultValue="off">
+ <a:documentation>This specifies that file and line should
+be printed.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="function_name" a:defaultValue="off">
+ <a:documentation>This specifies that the code function name
+should be printed.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <ref name="macro.logging_attributes"/>
+ <zeroOrMore>
+ <ref name="logger_subsys"/>
+ </zeroOrMore>
+ </group>
+ </element>
+ </define>
+ <define name="logger_subsys">
+ <element name="logger_subsys">
+ <group>
+ <ref name="macro.logging_attributes"/>
+ <attribute name="subsys">
+ <a:documentation>This specifies the subsystem identity
+(name) for which logging is specified. This is the name used by
+a service in the `log_init` call. E.g., 'CPG'.
+This option is required.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <text/>
+ </attribute>
+ </group>
+ </element>
+ </define>
+ <define name="nodelist">
+ <element name="nodelist">
+ <optional>
+ <ref name="node"/>
+ </optional>
+ </element>
+ </define>
+ <define name="node">
+ <element name="node">
+ <group>
+ <optional>
+ <attribute name="noid">
+ <a:documentation>This configuration option is optional when
+using IPv4 and required when using IPv6. This is a 32 bit value
+specifying the node identifier delivered to the cluster membership
+service. If this is not specified with IPv4, the node id will be
+determined from the 32 bit IP address the system to which the system
+is bound with ring identifier of 0. The node identifier value of zero
+is reserved and should not be used.</a:documentation>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <attribute name="ring0_addr">
+ <a:documentation>This specifies IP address of one of the nodes for particular ring
+as denoted by its number (instead 0, there can be higher numbers).</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <text/>
+ </attribute>
+ <optional>
+ <attribute name="ring1_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring2_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring3_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring4_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring5_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring6_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring7_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring8_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring9_addr"/>
+ </optional>
+ </group>
+ <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only -->
+ </element>
+ </define>
+ <define name="quorum">
+ <element name="quorum">
+ <group>
+ <optional>
+ <attribute name="allow_downscale" a:defaultValue="0">
+ <a:documentation>This enables Downscale feature
+(see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="auto_tie_breaker" a:defaultValue="0">
+ <a:documentation>This enables Auto Tie Breaker feature
+(see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="expected_votes">
+ <a:documentation>This specifies the number of expected votes, overriding the number
+implied by the number of *node* items within *nodes*.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="last_man_standing" a:defaultValue="0">
+ <a:documentation>This enables Last Man Standing feature
+(see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="last_man_standing_window" a:defaultValue="0">
+ <a:documentation>This specifies the tunable for Last Man
+Standing feature (see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="nonNegativeInteger"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="provider">
+ <a:documentation>This specifies the quorum algorithm to use.
+As of now, only 'corosync_votequorum' is supported.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <value>corosync_votequorum</value>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="two_node" a:defaultValue="0">
+ <a:documentation>This enables two node cluster operations
+(see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="wait_for_all" a:defaultValue="0">
+ <a:documentation>This enables Wait For All feature
+(see `votequorum(5)`).</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ </group>
+ </element>
+ </define>
+ <define name="totem">
+ <element name="totem">
+ <group>
+ <optional>
+ <attribute name="clear_node_high_bit" a:defaultValue="no"
+ a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade).">
+ <a:documentation>This configuration option is optional and
+is only relevant when no *nodeid* is specified. Some corosync clients
+require a signed 32 bit nodeid that is greater than zero however by
+default corosync uses all 32 bits of the IPv4 address space when
+generating a nodeid. Set this option to 'yes' to force the high bit
+to be zero and therefor ensure the nodeid is a positive signed 32 bit
+integer.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="cluster_name">
+ <a:documentation>This specifies the name of cluster and it's
+used for automatic generating of multicast address.</a:documentation>
+ <!--
+ CFG: corosync.conf, cluster.conf
+ NOTE: not a direct mapping in cluster.conf (top-level tag instead)
+ -->
+ <text/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="consensus" a:defaultValue="1200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for consensus to be achieved before starting a new round
+of membership configuration. The minimum value for *consensus* must be
+1.2 * *token*.
+
+This value will be automatically calculated at 1.2 * *token* if
+the user doesn't specify a *consensus* value.
+
+For two node clusters, a *consensus* larger then the *join* timeout but
+less then *token* is safe. For three node or larger clusters,
+*consensus* should be larger then token. There is an increasing risk
+of odd membership changes, which still guarantee virtual synchrony,
+as node count grows if *consensus* is less than *token*.</a:documentation>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="crypto_cipher" a:defaultValue="aes256">
+ <a:documentation>This specifies which cipher should be used
+to encrypt all messages. Valid values are 'none' (no encryption),
+'aes256', 'aes192', 'aes128' and '3des'.</a:documentation>
+ <!--
+ XXX: missing nss?
+ CFG: corosync.conf
+ -->
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>none</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="crypto_compat">
+ <!-- CFG: undocumented -->
+ <choice>
+ <value>2.0</value>
+ <value>2.2</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="crypto_hash" a:defaultValue="sha1">
+ <a:documentation>This specifies which HMAC authentication
+should be used to authenticate all messages. Valid values are 'none'
+(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>none</value>
+ <value>md5</value>
+ <value>sha1</value>
+ <value>sha256</value>
+ <value>sha384</value>
+ <value>sha512</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="crypto_type">
+ <!-- CFG: undocumented -->
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>nss</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="downcheck" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking that a network interface is back up after
+it has been downed.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="fail_recv_const" a:defaultValue="2500">
+ <a:documentation>This constant specifies how many rotations
+of the token without receiving any of the messages when messages should
+be received may occur before a new configuration is formed.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="heartbeat_failures_allowed" a:defaultValue="0">
+ <a:documentation>Configures the optional HeartBeating
+mechanism for faster failure detection. Keep in mind that engaging this
+mechanism in lossy networks could cause faulty loss declaration as
+the mechanism relies on the network for heartbeating.
+
+So as a rule of thumb use this mechanism if you require improved
+failure in low to medium utilized networks.
+
+This constant specifies the number of heartbeat failures the system
+should tolerate before declaring heartbeat failure, e.g., 3.
+Also if this value is not set or is 0 then the heartbeat mechanism is
+not engaged in the system and token rotation is the method of failure
+detection. Zero disables the mechanism.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="hold" a:defaultValue="180"
+ a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+how long the token should be held by the representative when
+the protocol is under low utilization.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="join" a:defaultValue="50">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for join messages in the membership protocol.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="max_messages" a:defaultValue="17">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent by one processor on receipt of the token.
+The *max_messages* parameter is limited to 256000 / *netmtu* to prevent
+overflow of the kernel transmit buffers.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="max_network_delay" a:defaultValue="50">
+ <a:documentation>This constant specifies in milliseconds
+the approximate delay that your network takes to transport one packet
+from one machine to another. This value is to be set by system engineers
+and please don't change it if not sure as this effects the failure
+detection mechanism using heartbeat.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="merge" a:defaultValue="200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking for a partition when no multicast traffic
+is being sent. If multicast traffic is being sent, the merge detection
+happens automatically as a function of the protocol.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="miss_count_const" a:defaultValue="5">
+ <a:documentation>This constant defines the maximum number
+of times on receipt of a token a message is checked for retransmission
+before a retransmission occurs. This parameter is useful to modify for
+switches that delay multicast packets compared to unicast packets.
+The default setting works well for nearly all modern switches.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="netmtu" a:defaultValue="1500">
+ <a:documentation>This specifies the network maximum transmit
+unit. To set this value beyond 1500, the regular frame MTU, requires
+ethernet devices that support large, or also called jumbo, frames.
+If any device in the network doesn't support large frames, the protocol
+will not operate properly. The hosts must also have their mtu size set
+from 1500 to whatever frame size is specified here.
+
+Please note while some NICs or switches
+claim large frame support, they support 9000 MTU as the maximum frame
+size including the IP header. Setting the *netmtu* and host MTUs to 9000
+will cause totem to use the full 9000 bytes of the frame. Then Linux
+will add a 18 byte header moving the full frame size to 9018.
+As a result some hardware will not operate properly with this size
+of data. A *netmtu* of 8982 seems to work for the few large frame devices
+that have been tested. Some manufacturers claim large frame support
+when in fact they support frame sizes of 4500 bytes.
+
+When sending multicast traffic, if the network frequently reconfigures,
+chances are that some device in the network doesn't support large frames.
+
+Choose hardware carefully if intending to use large frame support.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="nodeid">
+ <!-- CFG: undocumented -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000">
+ <a:documentation>This specifies the time in milliseconds
+to check if the failed ring can be auto-recovered.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_mode">
+ <a:documentation>This specifies the mode of redundant ring,
+which may be 'none', 'active', or 'passive'. Active replication offers
+none`, active, or passive. Active replication offers
+slightly lower latency from transmit to delivery in faulty network
+environments but with less performance. Passive replication may nearly
+double the speed of the totem protocol if the protocol doesn't become
+CPU bound. The final option is none, in which case only one network
+interface will be used to operate the totem protocol.
+
+If only one *interface* directive is specified, 'none' is automatically
+chosen. If multiple *interface* directives are specified, only 'active'
+or 'passive' may be chosen.
+
+The maximum number of *interface* directives that is allowed for either
+mode ('active' or 'passive') is 2.</a:documentation>
+ <!--
+ XXX: implied check: active,passive -> count(interface) <= 2
+ CFG: corosync.conf, cluster.conf
+ -->
+ <choice>
+ <value>active</value>
+ <value>none</value>
+ <value>passive</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_problem_count_mcast_threshold">
+ <a:documentation>This specifies the number of times
+a problem is detected with multicast before setting the link faulty for
+passive RRP mode. This variable is unused in active RRP mode.
+
+The default is 10 times *rrp_problem_count_threshold*.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_problem_count_threshold" a:defaultValue="10">
+ <a:documentation>This specifies the number of times
+a problem is detected with a link before setting the link faulty.
+Once a link is set faulty, no more data is transmitted upon it. Also,
+the problem counter is no longer decremented when the problem count
+timeout expires.
+
+A problem is detected whenever all tokens from the proceeding
+processor have not been received within the *rrp_token_expired_timeout*.
+The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be
+at least 50 milliseconds less then the *token* timeout, or a complete
+reconfiguration may occur.</a:documentation>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_problem_count_timeout" a:defaultValue="2000">
+ <a:documentation>This specifies the time in milliseconds
+to wait before decrementing the problem count by 1 for a particular ring
+to ensure a link is not marked faulty for transient network failures.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="rrp_token_expired_timeout" a:defaultValue="47"
+ a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This specifies the time in milliseconds
+to increment the problem counter for the redundant ring protocol after
+not having received a token from all rings for a particular processor.
+
+This value will automatically be calculated from the *token* timeout
+and *problem_count_threshold* but may be overridden.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="secauth" a:defaultValue="on"
+ a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*.">
+ <a:documentation>This specifies that HMAC/SHA1 authentication should be used
+to authenticate all messages. It further specifies that all data
+should be encrypted with the nss library and aes256 encryption
+algorithm to protect data from eavesdropping.
+
+Enabling this option adds a encryption header to every message sent
+by totem which reduces total throughput. Also encryption and
+authentication consume extra CPU cycles in corosync.</a:documentation>
+ <!--
+ XXX: implied check/migration to current items
+ CFG: corosync.conf, cluster.conf
+ -->
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="send_join" a:defaultValue="0"
+ a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations.">
+ <a:documentation>This timeout specifies in milliseconds
+an upper range between 0 and *send_join* to wait before sending a join
+message. For eprecationtions with less then 32 nodes, this parameter
+is not necessary. For larger rings, this parameter is necessary
+to ensure the NIC is not overflowed with join messages on formation of
+a new ring. A reasonable value for large rings (128 nodes) would be
+80msec. Other timer values must also change if this value is changed.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="seqno_unchanged_const" a:defaultValue="30">
+ <a:documentation>This constant specifies how many rotations
+of the token without any multicast traffic should occur before the hold
+timer is started.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="threads">
+ <!-- CFG: undocumented -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="token" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds
+until a token loss is declared after not receiving a token. This is
+the time spent detecting a failure of a processor in the current
+configuration. Reforming a new configuration takes about 50
+milliseconds in addition to this timeout.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="token_retransmit" a:defaultValue="238"
+ a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+after how long before receiving a token the token is retransmitted.
+This will be automatically calculated if token is modified.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="token_retransmits_before_loss_const" a:defaultValue="4">
+ <a:documentation>This value identifies how many token
+retransmits should be attempted before forming a new configuration.
+If this value is set, retransmit and hold will be automatically
+calculated from *retransmits_before_loss* and token.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="transport" a:defaultValue="udp">
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>iba</value>
+ <value>udp</value>
+ <value>udpu</value>
+ </choice>
+ </attribute>
+ </optional>
+ <attribute name="version">
+ <a:documentation>This option controls the transport
+mechanism used. If the interface to which corosync is binding is
+an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter
+may be specified. To avoid the use of multicast entirely, a unicast
+transport parameter 'udpu' can be specified. This requires specifying
+the list of members in *nodelist* directive, that could potentially make
+up the membership before deployment.</a:documentation>
+ <a:documentation>This specifies the version of
+the configuration file. Currently the only valid value for this
+option is '2'.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ <optional>
+ <attribute name="vsftype" a:defaultValue="ykd">
+ <a:documentation>This option controls the virtual
+synchrony filter type used to identify a primary component.
+The preferred choice is YKD dynamic linear voting, however, for
+clusters larger then 32 nodes YKD consumes alot of memory. For large
+scale clusters that are created by changing the MAX_PROCESSORS_COUNT
+#define in the C code totem.h file, the virtual synchrony filter 'none'
+is recommended but then AMF and DLCK services (which are currently
+experimental) are not safe for use.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>none</value>
+ <value>ykd</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="window_size" a:defaultValue="50">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent on one token rotation. If all processors
+perform equally well, this value could be large (300), which would
+introduce higher latency from origination to delivery for very large
+rings. To reduce latency in large rings (16+), the defaults are a safe
+compromise. If 1 or more slow processor(s) are present among fast
+processors, *window_size* should be no larger then 256000 / *netmtu*
+to avoid overflow of the kernel receive buffers. The user is notified
+of this by the display of a retransmit list in the notification logs.
+There is no loss of data, but performance is reduced when these errors
+occur.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <ref name="interface"/>
+ </zeroOrMore>
+ </group>
+ </element>
+ </define>
+ <define name="interface">
+ <element name="interface">
+ <group>
+ <optional>
+ <attribute name="bindnetaddr">
+ <a:documentation>This specifies the network address
+the corosync executive should bind to.
+*bindnetaddr* should be an IP address configured on the system, or
+a network address.
+
+For example, if the local interface is `192.168.5.92` with netmask
+`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or
+`192.168.5.0`. If the local interface is `192.168.5.92` with netmask
+`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`,
+and so forth.
+
+This may also be an IPv6 address, in which case IPv6 networking will be
+used. In this case, the exact address must be specified and there is no
+automatic selection of the network interface within a specific subnet
+as with IPv4.
+
+If IPv6 networking is used, the *nodeid* field in *nodelist* must be
+specified.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <text/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="broadcast" a:defaultValue="no">
+ <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes',
+the broadcast address will be used for communication. If this option
+is set, *mcastaddr* should not be set.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="mcastaddr">
+ <a:documentation>This is the multicast address used
+by corosync executive. The default should work for most networks, but
+the network administrator should be queried about a multicast address
+to use. Avoid `224.x.x.x` because this is a "config" multicast address.
+
+This may also be an IPv6 multicast address, in which case IPv6 networking
+will be used. If IPv6 networking is used, the *nodeid* field in
+*nodelist* must be specified.
+
+It's not needed to use this option if *cluster_name* option is used.
+If both options are used, *mcastaddr* has higher priority.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <text/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="mcastport">
+ <a:documentation>This specifies the UDP port number.
+It is possible to use the same multicast address on a network with
+the corosync services configured for different UDP ports. Please note
+corosync uses two UDP ports *mcastport* (for mcast receives) and
+*mcastport* - 1 (for mcast sends). If you have multiple clusters
+on the same network using the same *mcastaddr* please configure
+the **mcastport**s with a gap.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedShort"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="ringnumber">
+ <a:documentation>This specifies the ring number for
+the interface. When using the redundant ring protocol, each interface
+should specify separate ring numbers to uniquely identify to
+the membership protocol which interface to use for which redundant ring.
+The *ringnumber* must start at 0.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ <optional>
+ <attribute name="ttl" a:defaultValue="1">
+ <a:documentation>This specifies the Time To Live (TTL).
+If you run your cluster on a routed network then the default of '1' will
+be too small. This option provides a way to increase this up to '255'.
+The valid range is '0..255'. Note that this is only valid on multicast
+transport types.</a:documentation>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ </group>
+ </element>
+ </define>
+ <!--
+ uidgid =
+ element uidgid {
+ attribute uid {text}?,
+ attribute gid {text}?
+ }
+ -->
+</grammar>
diff --git a/corosync.rng.trang b/corosync.rng.trang
new file mode 100644
index 0000000..e53e6a0
--- /dev/null
+++ b/corosync.rng.trang
@@ -0,0 +1,907 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ style guide (RELAX NG Compact only):
+ * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence
+ separator, AsciiDoc formatting is used within them
+ * sort everything you can to ease the lookup
+-->
+<grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <start>
+ <ref name="corosync"/>
+ </start>
+ <define name="corosync">
+ <element name="corosync">
+ <interleave>
+ <optional>
+ <ref name="logging"/>
+ </optional>
+ <optional>
+ <ref name="nodelist"/>
+ </optional>
+ <optional>
+ <ref name="quorum"/>
+ </optional>
+ <ref name="totem"/>
+ <optional>
+ <ref name="uidgid"/>
+ </optional>
+ </interleave>
+ </element>
+ </define>
+ <define name="macro.logging_attributes">
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="debug" a:defaultValue="off">
+ <a:documentation>This specifies whether debug output is
+logged for this particular logger. Also can contain value trace, what
+is highest level of debug informations.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="logfile_priority" a:defaultValue="info">
+ <a:documentation>This specifies the logfile level for this particular subsystem. Ignored
+if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug'
+(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation>
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="syslog_facility" a:defaultValue="daemon">
+ <a:documentation>This specifies the syslog facility type
+that will be used for any messages sent to syslog. Options are
+'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5',
+'local6' and 'local7'.</a:documentation>
+ <choice>
+ <value>daemon</value>
+ <value>local0</value>
+ <value>local1</value>
+ <value>local2</value>
+ <value>local3</value>
+ <value>local4</value>
+ <value>local5</value>
+ <value>local6</value>
+ <value>local7</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="syslog_priority" a:defaultValue="info">
+ <a:documentation>This specifies the syslog level for this
+particular subsystem. Ignored if *debug* is 'on'. Possible values are:
+'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err',
+'info', 'notice' and 'warning'.</a:documentation>
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="to_logfile" a:defaultValue="no">
+ <a:documentation>This specifies the destination
+of logging output.
+
+Please note, if you are using *to_logfile* and want to rotate the file,
+use `logrotate(8)` with the option `copytruncate`, e.g.
+
+----
+/var/log/corosync.log {
+ missingok
+ compress
+ notifempty
+ daily
+ rotate 7
+ copytruncate
+}
+----</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="to_stderr" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="to_syslog" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ </define>
+ <define name="logging">
+ <element name="logging">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="timestamp" a:defaultValue="off">
+ <a:documentation>This specifies that a timestamp is placed
+on all log messages.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="fileline" a:defaultValue="off">
+ <a:documentation>This specifies that file and line should
+be printed.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="function_name" a:defaultValue="off">
+ <a:documentation>This specifies that the code function name
+should be printed.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <ref name="macro.logging_attributes"/>
+ <zeroOrMore>
+ <ref name="logger_subsys"/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="logger_subsys">
+ <element name="logger_subsys">
+ <ref name="macro.logging_attributes"/>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="subsys">
+ <a:documentation>This specifies the subsystem identity
+(name) for which logging is specified. This is the name used by
+a service in the `log_init` call. E.g., 'CPG'.
+This option is required.</a:documentation>
+ </attribute>
+ </element>
+ </define>
+ <define name="nodelist">
+ <element name="nodelist">
+ <optional>
+ <ref name="node"/>
+ </optional>
+ </element>
+ </define>
+ <define name="node">
+ <element name="node">
+ <group>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="noid">
+ <a:documentation>This configuration option is optional when
+using IPv4 and required when using IPv6. This is a 32 bit value
+specifying the node identifier delivered to the cluster membership
+service. If this is not specified with IPv4, the node id will be
+determined from the 32 bit IP address the system to which the system
+is bound with ring identifier of 0. The node identifier value of zero
+is reserved and should not be used.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ring0_addr">
+ <a:documentation>This specifies IP address of one of the nodes for particular ring
+as denoted by its number (instead 0, there can be higher numbers).</a:documentation>
+ </attribute>
+ <optional>
+ <attribute name="ring1_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring2_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring3_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring4_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring5_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring6_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring7_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring8_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring9_addr"/>
+ </optional>
+ </group>
+ <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only -->
+ </element>
+ </define>
+ <define name="quorum">
+ <element name="quorum">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="allow_downscale" a:defaultValue="0">
+ <a:documentation>This enables Downscale feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="auto_tie_breaker" a:defaultValue="0">
+ <a:documentation>This enables Auto Tie Breaker feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="expected_votes">
+ <a:documentation>This specifies the number of expected votes, overriding the number
+implied by the number of *node* items within *nodes*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="last_man_standing" a:defaultValue="0">
+ <a:documentation>This enables Last Man Standing feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="last_man_standing_window" a:defaultValue="0">
+ <a:documentation>This specifies the tunable for Last Man
+Standing feature (see `votequorum(5)`).</a:documentation>
+ <data type="nonNegativeInteger"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="provider">
+ <a:documentation>This specifies the quorum algorithm to use.
+As of now, only 'corosync_votequorum' is supported.</a:documentation>
+ <value>corosync_votequorum</value>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="two_node" a:defaultValue="0">
+ <a:documentation>This enables two node cluster operations
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="wait_for_all" a:defaultValue="0">
+ <a:documentation>This enables Wait For All feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ </element>
+ </define>
+ <define name="totem">
+ <element name="totem">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="clear_node_high_bit" a:defaultValue="no" a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade).">
+ <a:documentation>This configuration option is optional and
+is only relevant when no *nodeid* is specified. Some corosync clients
+require a signed 32 bit nodeid that is greater than zero however by
+default corosync uses all 32 bits of the IPv4 address space when
+generating a nodeid. Set this option to 'yes' to force the high bit
+to be zero and therefor ensure the nodeid is a positive signed 32 bit
+integer.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ CFG: corosync.conf, cluster.conf
+ NOTE: not a direct mapping in cluster.conf (top-level tag instead)
+ -->
+ <attribute name="cluster_name">
+ <a:documentation>This specifies the name of cluster and it's
+used for automatic generating of multicast address.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="consensus" a:defaultValue="1200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for consensus to be achieved before starting a new round
+of membership configuration. The minimum value for *consensus* must be
+1.2 * *token*.
+
+This value will be automatically calculated at 1.2 * *token* if
+the user doesn't specify a *consensus* value.
+
+For two node clusters, a *consensus* larger then the *join* timeout but
+less then *token* is safe. For three node or larger clusters,
+*consensus* should be larger then token. There is an increasing risk
+of odd membership changes, which still guarantee virtual synchrony,
+as node count grows if *consensus* is less than *token*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: missing nss?
+ CFG: corosync.conf
+ -->
+ <attribute name="crypto_cipher" a:defaultValue="aes256">
+ <a:documentation>This specifies which cipher should be used
+to encrypt all messages. Valid values are 'none' (no encryption),
+'aes256', 'aes192', 'aes128' and '3des'.</a:documentation>
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>none</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="crypto_compat">
+ <choice>
+ <value>2.0</value>
+ <value>2.2</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="crypto_hash" a:defaultValue="sha1">
+ <a:documentation>This specifies which HMAC authentication
+should be used to authenticate all messages. Valid values are 'none'
+(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation>
+ <choice>
+ <value>none</value>
+ <value>md5</value>
+ <value>sha1</value>
+ <value>sha256</value>
+ <value>sha384</value>
+ <value>sha512</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="crypto_type">
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>nss</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="downcheck" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking that a network interface is back up after
+it has been downed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="fail_recv_const" a:defaultValue="2500">
+ <a:documentation>This constant specifies how many rotations
+of the token without receiving any of the messages when messages should
+be received may occur before a new configuration is formed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="heartbeat_failures_allowed" a:defaultValue="0">
+ <a:documentation>Configures the optional HeartBeating
+mechanism for faster failure detection. Keep in mind that engaging this
+mechanism in lossy networks could cause faulty loss declaration as
+the mechanism relies on the network for heartbeating.
+
+So as a rule of thumb use this mechanism if you require improved
+failure in low to medium utilized networks.
+
+This constant specifies the number of heartbeat failures the system
+should tolerate before declaring heartbeat failure, e.g., 3.
+Also if this value is not set or is 0 then the heartbeat mechanism is
+not engaged in the system and token rotation is the method of failure
+detection. Zero disables the mechanism.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="hold" a:defaultValue="180" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+how long the token should be held by the representative when
+the protocol is under low utilization.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="join" a:defaultValue="50">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for join messages in the membership protocol.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="max_messages" a:defaultValue="17">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent by one processor on receipt of the token.
+The *max_messages* parameter is limited to 256000 / *netmtu* to prevent
+overflow of the kernel transmit buffers.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="max_network_delay" a:defaultValue="50">
+ <a:documentation>This constant specifies in milliseconds
+the approximate delay that your network takes to transport one packet
+from one machine to another. This value is to be set by system engineers
+and please don't change it if not sure as this effects the failure
+detection mechanism using heartbeat.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="merge" a:defaultValue="200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking for a partition when no multicast traffic
+is being sent. If multicast traffic is being sent, the merge detection
+happens automatically as a function of the protocol.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="miss_count_const" a:defaultValue="5">
+ <a:documentation>This constant defines the maximum number
+of times on receipt of a token a message is checked for retransmission
+before a retransmission occurs. This parameter is useful to modify for
+switches that delay multicast packets compared to unicast packets.
+The default setting works well for nearly all modern switches.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="netmtu" a:defaultValue="1500">
+ <a:documentation>This specifies the network maximum transmit
+unit. To set this value beyond 1500, the regular frame MTU, requires
+ethernet devices that support large, or also called jumbo, frames.
+If any device in the network doesn't support large frames, the protocol
+will not operate properly. The hosts must also have their mtu size set
+from 1500 to whatever frame size is specified here.
+
+Please note while some NICs or switches
+claim large frame support, they support 9000 MTU as the maximum frame
+size including the IP header. Setting the *netmtu* and host MTUs to 9000
+will cause totem to use the full 9000 bytes of the frame. Then Linux
+will add a 18 byte header moving the full frame size to 9018.
+As a result some hardware will not operate properly with this size
+of data. A *netmtu* of 8982 seems to work for the few large frame devices
+that have been tested. Some manufacturers claim large frame support
+when in fact they support frame sizes of 4500 bytes.
+
+When sending multicast traffic, if the network frequently reconfigures,
+chances are that some device in the network doesn't support large frames.
+
+Choose hardware carefully if intending to use large frame support.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="nodeid">
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000">
+ <a:documentation>This specifies the time in milliseconds
+to check if the failed ring can be auto-recovered.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check: active,passive -> count(interface) <= 2
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="rrp_mode">
+ <a:documentation>This specifies the mode of redundant ring,
+which may be 'none', 'active', or 'passive'. Active replication offers
+none`, active, or passive. Active replication offers
+slightly lower latency from transmit to delivery in faulty network
+environments but with less performance. Passive replication may nearly
+double the speed of the totem protocol if the protocol doesn't become
+CPU bound. The final option is none, in which case only one network
+interface will be used to operate the totem protocol.
+
+If only one *interface* directive is specified, 'none' is automatically
+chosen. If multiple *interface* directives are specified, only 'active'
+or 'passive' may be chosen.
+
+The maximum number of *interface* directives that is allowed for either
+mode ('active' or 'passive') is 2.</a:documentation>
+ <choice>
+ <value>active</value>
+ <value>none</value>
+ <value>passive</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_problem_count_mcast_threshold">
+ <a:documentation>This specifies the number of times
+a problem is detected with multicast before setting the link faulty for
+passive RRP mode. This variable is unused in active RRP mode.
+
+The default is 10 times *rrp_problem_count_threshold*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="rrp_problem_count_threshold" a:defaultValue="10">
+ <a:documentation>This specifies the number of times
+a problem is detected with a link before setting the link faulty.
+Once a link is set faulty, no more data is transmitted upon it. Also,
+the problem counter is no longer decremented when the problem count
+timeout expires.
+
+A problem is detected whenever all tokens from the proceeding
+processor have not been received within the *rrp_token_expired_timeout*.
+The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be
+at least 50 milliseconds less then the *token* timeout, or a complete
+reconfiguration may occur.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_problem_count_timeout" a:defaultValue="2000">
+ <a:documentation>This specifies the time in milliseconds
+to wait before decrementing the problem count by 1 for a particular ring
+to ensure a link is not marked faulty for transient network failures.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_token_expired_timeout" a:defaultValue="47" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This specifies the time in milliseconds
+to increment the problem counter for the redundant ring protocol after
+not having received a token from all rings for a particular processor.
+
+This value will automatically be calculated from the *token* timeout
+and *problem_count_threshold* but may be overridden.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check/migration to current items
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="secauth" a:defaultValue="on" a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*.">
+ <a:documentation>This specifies that HMAC/SHA1 authentication should be used
+to authenticate all messages. It further specifies that all data
+should be encrypted with the nss library and aes256 encryption
+algorithm to protect data from eavesdropping.
+
+Enabling this option adds a encryption header to every message sent
+by totem which reduces total throughput. Also encryption and
+authentication consume extra CPU cycles in corosync.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="send_join" a:defaultValue="0" a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations.">
+ <a:documentation>This timeout specifies in milliseconds
+an upper range between 0 and *send_join* to wait before sending a join
+message. For eprecationtions with less then 32 nodes, this parameter
+is not necessary. For larger rings, this parameter is necessary
+to ensure the NIC is not overflowed with join messages on formation of
+a new ring. A reasonable value for large rings (128 nodes) would be
+80msec. Other timer values must also change if this value is changed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="seqno_unchanged_const" a:defaultValue="30">
+ <a:documentation>This constant specifies how many rotations
+of the token without any multicast traffic should occur before the hold
+timer is started.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="threads">
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="token" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds
+until a token loss is declared after not receiving a token. This is
+the time spent detecting a failure of a processor in the current
+configuration. Reforming a new configuration takes about 50
+milliseconds in addition to this timeout.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="token_retransmit" a:defaultValue="238" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+after how long before receiving a token the token is retransmitted.
+This will be automatically calculated if token is modified.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="token_retransmits_before_loss_const" a:defaultValue="4">
+ <a:documentation>This value identifies how many token
+retransmits should be attempted before forming a new configuration.
+If this value is set, retransmit and hold will be automatically
+calculated from *retransmits_before_loss* and token.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="transport" a:defaultValue="udp">
+ <choice>
+ <value>iba</value>
+ <value>udp</value>
+ <value>udpu</value>
+ </choice>
+ </attribute>
+ </optional>
+ <attribute name="version">
+ <a:documentation>This option controls the transport
+mechanism used. If the interface to which corosync is binding is
+an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter
+may be specified. To avoid the use of multicast entirely, a unicast
+transport parameter 'udpu' can be specified. This requires specifying
+the list of members in *nodelist* directive, that could potentially make
+up the membership before deployment.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <a:documentation>This specifies the version of
+the configuration file. Currently the only valid value for this
+option is '2'.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="vsftype" a:defaultValue="ykd">
+ <a:documentation>This option controls the virtual
+synchrony filter type used to identify a primary component.
+The preferred choice is YKD dynamic linear voting, however, for
+clusters larger then 32 nodes YKD consumes alot of memory. For large
+scale clusters that are created by changing the MAX_PROCESSORS_COUNT
+#define in the C code totem.h file, the virtual synchrony filter 'none'
+is recommended but then AMF and DLCK services (which are currently
+experimental) are not safe for use.</a:documentation>
+ <choice>
+ <value>none</value>
+ <value>ykd</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="window_size" a:defaultValue="50">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent on one token rotation. If all processors
+perform equally well, this value could be large (300), which would
+introduce higher latency from origination to delivery for very large
+rings. To reduce latency in large rings (16+), the defaults are a safe
+compromise. If 1 or more slow processor(s) are present among fast
+processors, *window_size* should be no larger then 256000 / *netmtu*
+to avoid overflow of the kernel receive buffers. The user is notified
+of this by the display of a retransmit list in the notification logs.
+There is no loss of data, but performance is reduced when these errors
+occur.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <ref name="interface"/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="interface">
+ <element name="interface">
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="bindnetaddr">
+ <a:documentation>This specifies the network address
+the corosync executive should bind to.
+*bindnetaddr* should be an IP address configured on the system, or
+a network address.
+
+For example, if the local interface is `192.168.5.92` with netmask
+`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or
+`192.168.5.0`. If the local interface is `192.168.5.92` with netmask
+`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`,
+and so forth.
+
+This may also be an IPv6 address, in which case IPv6 networking will be
+used. In this case, the exact address must be specified and there is no
+automatic selection of the network interface within a specific subnet
+as with IPv4.
+
+If IPv6 networking is used, the *nodeid* field in *nodelist* must be
+specified.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="broadcast" a:defaultValue="no">
+ <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes',
+the broadcast address will be used for communication. If this option
+is set, *mcastaddr* should not be set.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="mcastaddr">
+ <a:documentation>This is the multicast address used
+by corosync executive. The default should work for most networks, but
+the network administrator should be queried about a multicast address
+to use. Avoid `224.x.x.x` because this is a "config" multicast address.
+
+This may also be an IPv6 multicast address, in which case IPv6 networking
+will be used. If IPv6 networking is used, the *nodeid* field in
+*nodelist* must be specified.
+
+It's not needed to use this option if *cluster_name* option is used.
+If both options are used, *mcastaddr* has higher priority.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="mcastport">
+ <a:documentation>This specifies the UDP port number.
+It is possible to use the same multicast address on a network with
+the corosync services configured for different UDP ports. Please note
+corosync uses two UDP ports *mcastport* (for mcast receives) and
+*mcastport* - 1 (for mcast sends). If you have multiple clusters
+on the same network using the same *mcastaddr* please configure
+the **mcastport**s with a gap.</a:documentation>
+ <data type="unsignedShort"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ringnumber">
+ <a:documentation>This specifies the ring number for
+the interface. When using the redundant ring protocol, each interface
+should specify separate ring numbers to uniquely identify to
+the membership protocol which interface to use for which redundant ring.
+The *ringnumber* must start at 0.</a:documentation>
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ttl" a:defaultValue="1">
+ <a:documentation>This specifies the Time To Live (TTL).
+If you run your cluster on a routed network then the default of '1' will
+be too small. This option provides a way to increase this up to '255'.
+The valid range is '0..255'. Note that this is only valid on multicast
+transport types.</a:documentation>
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ uidgid =
+ element uidgid {
+ attribute uid {text}?,
+ attribute gid {text}?
+ }
+-->
diff --git a/corosync.trang.rng b/corosync.trang.rng
new file mode 100644
index 0000000..e53e6a0
--- /dev/null
+++ b/corosync.trang.rng
@@ -0,0 +1,907 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ style guide (RELAX NG Compact only):
+ * '##' comments are wrapped at 50/80 (first line/rest), 2-spaced sentence
+ separator, AsciiDoc formatting is used within them
+ * sort everything you can to ease the lookup
+-->
+<grammar xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" xmlns:a4doc="http://people.redhat.com/jpokorny/ns/a4doc" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+ <start>
+ <ref name="corosync"/>
+ </start>
+ <define name="corosync">
+ <element name="corosync">
+ <interleave>
+ <optional>
+ <ref name="logging"/>
+ </optional>
+ <optional>
+ <ref name="nodelist"/>
+ </optional>
+ <optional>
+ <ref name="quorum"/>
+ </optional>
+ <ref name="totem"/>
+ <optional>
+ <ref name="uidgid"/>
+ </optional>
+ </interleave>
+ </element>
+ </define>
+ <define name="macro.logging_attributes">
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="debug" a:defaultValue="off">
+ <a:documentation>This specifies whether debug output is
+logged for this particular logger. Also can contain value trace, what
+is highest level of debug informations.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="logfile_priority" a:defaultValue="info">
+ <a:documentation>This specifies the logfile level for this particular subsystem. Ignored
+if *debug* is 'on'. Possible values are: 'alert', 'crit', 'debug'
+(same as *debug =* 'on'), 'emerg', 'err', 'info', 'notice' and 'warning'.</a:documentation>
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="syslog_facility" a:defaultValue="daemon">
+ <a:documentation>This specifies the syslog facility type
+that will be used for any messages sent to syslog. Options are
+'daemon', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5',
+'local6' and 'local7'.</a:documentation>
+ <choice>
+ <value>daemon</value>
+ <value>local0</value>
+ <value>local1</value>
+ <value>local2</value>
+ <value>local3</value>
+ <value>local4</value>
+ <value>local5</value>
+ <value>local6</value>
+ <value>local7</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="syslog_priority" a:defaultValue="info">
+ <a:documentation>This specifies the syslog level for this
+particular subsystem. Ignored if *debug* is 'on'. Possible values are:
+'alert', 'crit', 'debug' (same as *debug =* 'on'), 'emerg', 'err',
+'info', 'notice' and 'warning'.</a:documentation>
+ <choice>
+ <value>alert</value>
+ <value>crit</value>
+ <value>debug</value>
+ <value>emerg</value>
+ <value>err</value>
+ <value>info</value>
+ <value>notice</value>
+ <value>warning</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="to_logfile" a:defaultValue="no">
+ <a:documentation>This specifies the destination
+of logging output.
+
+Please note, if you are using *to_logfile* and want to rotate the file,
+use `logrotate(8)` with the option `copytruncate`, e.g.
+
+----
+/var/log/corosync.log {
+ missingok
+ compress
+ notifempty
+ daily
+ rotate 7
+ copytruncate
+}
+----</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="to_stderr" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="to_syslog" a:defaultValue="yes">
+ <a:documentation>This specifies the destination
+of logging output.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ </define>
+ <define name="logging">
+ <element name="logging">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="timestamp" a:defaultValue="off">
+ <a:documentation>This specifies that a timestamp is placed
+on all log messages.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="fileline" a:defaultValue="off">
+ <a:documentation>This specifies that file and line should
+be printed.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="function_name" a:defaultValue="off">
+ <a:documentation>This specifies that the code function name
+should be printed.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <ref name="macro.logging_attributes"/>
+ <zeroOrMore>
+ <ref name="logger_subsys"/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="logger_subsys">
+ <element name="logger_subsys">
+ <ref name="macro.logging_attributes"/>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="subsys">
+ <a:documentation>This specifies the subsystem identity
+(name) for which logging is specified. This is the name used by
+a service in the `log_init` call. E.g., 'CPG'.
+This option is required.</a:documentation>
+ </attribute>
+ </element>
+ </define>
+ <define name="nodelist">
+ <element name="nodelist">
+ <optional>
+ <ref name="node"/>
+ </optional>
+ </element>
+ </define>
+ <define name="node">
+ <element name="node">
+ <group>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="noid">
+ <a:documentation>This configuration option is optional when
+using IPv4 and required when using IPv6. This is a 32 bit value
+specifying the node identifier delivered to the cluster membership
+service. If this is not specified with IPv4, the node id will be
+determined from the 32 bit IP address the system to which the system
+is bound with ring identifier of 0. The node identifier value of zero
+is reserved and should not be used.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ring0_addr">
+ <a:documentation>This specifies IP address of one of the nodes for particular ring
+as denoted by its number (instead 0, there can be higher numbers).</a:documentation>
+ </attribute>
+ <optional>
+ <attribute name="ring1_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring2_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring3_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring4_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring5_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring6_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring7_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring8_addr"/>
+ </optional>
+ <optional>
+ <attribute name="ring9_addr"/>
+ </optional>
+ </group>
+ <!-- NOTE: Augeas lens for corosync.conf counts on X = 0..9 only -->
+ </element>
+ </define>
+ <define name="quorum">
+ <element name="quorum">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="allow_downscale" a:defaultValue="0">
+ <a:documentation>This enables Downscale feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="auto_tie_breaker" a:defaultValue="0">
+ <a:documentation>This enables Auto Tie Breaker feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="expected_votes">
+ <a:documentation>This specifies the number of expected votes, overriding the number
+implied by the number of *node* items within *nodes*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="last_man_standing" a:defaultValue="0">
+ <a:documentation>This enables Last Man Standing feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="last_man_standing_window" a:defaultValue="0">
+ <a:documentation>This specifies the tunable for Last Man
+Standing feature (see `votequorum(5)`).</a:documentation>
+ <data type="nonNegativeInteger"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="provider">
+ <a:documentation>This specifies the quorum algorithm to use.
+As of now, only 'corosync_votequorum' is supported.</a:documentation>
+ <value>corosync_votequorum</value>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="two_node" a:defaultValue="0">
+ <a:documentation>This enables two node cluster operations
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="wait_for_all" a:defaultValue="0">
+ <a:documentation>This enables Wait For All feature
+(see `votequorum(5)`).</a:documentation>
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+ </attribute>
+ </optional>
+ </element>
+ </define>
+ <define name="totem">
+ <element name="totem">
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="clear_node_high_bit" a:defaultValue="no" a4doc:discretion-hint="The clusters behavior is undefined if this option is enabled on only a subset of the cluster (for example during a rolling upgrade).">
+ <a:documentation>This configuration option is optional and
+is only relevant when no *nodeid* is specified. Some corosync clients
+require a signed 32 bit nodeid that is greater than zero however by
+default corosync uses all 32 bits of the IPv4 address space when
+generating a nodeid. Set this option to 'yes' to force the high bit
+to be zero and therefor ensure the nodeid is a positive signed 32 bit
+integer.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ CFG: corosync.conf, cluster.conf
+ NOTE: not a direct mapping in cluster.conf (top-level tag instead)
+ -->
+ <attribute name="cluster_name">
+ <a:documentation>This specifies the name of cluster and it's
+used for automatic generating of multicast address.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="consensus" a:defaultValue="1200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for consensus to be achieved before starting a new round
+of membership configuration. The minimum value for *consensus* must be
+1.2 * *token*.
+
+This value will be automatically calculated at 1.2 * *token* if
+the user doesn't specify a *consensus* value.
+
+For two node clusters, a *consensus* larger then the *join* timeout but
+less then *token* is safe. For three node or larger clusters,
+*consensus* should be larger then token. There is an increasing risk
+of odd membership changes, which still guarantee virtual synchrony,
+as node count grows if *consensus* is less than *token*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: missing nss?
+ CFG: corosync.conf
+ -->
+ <attribute name="crypto_cipher" a:defaultValue="aes256">
+ <a:documentation>This specifies which cipher should be used
+to encrypt all messages. Valid values are 'none' (no encryption),
+'aes256', 'aes192', 'aes128' and '3des'.</a:documentation>
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>none</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="crypto_compat">
+ <choice>
+ <value>2.0</value>
+ <value>2.2</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="crypto_hash" a:defaultValue="sha1">
+ <a:documentation>This specifies which HMAC authentication
+should be used to authenticate all messages. Valid values are 'none'
+(no authentication), 'md5', 'sha1', 'sha256', 'sha384' and 'sha512'.</a:documentation>
+ <choice>
+ <value>none</value>
+ <value>md5</value>
+ <value>sha1</value>
+ <value>sha256</value>
+ <value>sha384</value>
+ <value>sha512</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="crypto_type">
+ <choice>
+ <value>3des</value>
+ <value>aes128</value>
+ <value>aes192</value>
+ <value>aes256</value>
+ <value>nss</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="downcheck" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking that a network interface is back up after
+it has been downed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="fail_recv_const" a:defaultValue="2500">
+ <a:documentation>This constant specifies how many rotations
+of the token without receiving any of the messages when messages should
+be received may occur before a new configuration is formed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="heartbeat_failures_allowed" a:defaultValue="0">
+ <a:documentation>Configures the optional HeartBeating
+mechanism for faster failure detection. Keep in mind that engaging this
+mechanism in lossy networks could cause faulty loss declaration as
+the mechanism relies on the network for heartbeating.
+
+So as a rule of thumb use this mechanism if you require improved
+failure in low to medium utilized networks.
+
+This constant specifies the number of heartbeat failures the system
+should tolerate before declaring heartbeat failure, e.g., 3.
+Also if this value is not set or is 0 then the heartbeat mechanism is
+not engaged in the system and token rotation is the method of failure
+detection. Zero disables the mechanism.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="hold" a:defaultValue="180" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+how long the token should be held by the representative when
+the protocol is under low utilization.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="join" a:defaultValue="50">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait for join messages in the membership protocol.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="max_messages" a:defaultValue="17">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent by one processor on receipt of the token.
+The *max_messages* parameter is limited to 256000 / *netmtu* to prevent
+overflow of the kernel transmit buffers.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="max_network_delay" a:defaultValue="50">
+ <a:documentation>This constant specifies in milliseconds
+the approximate delay that your network takes to transport one packet
+from one machine to another. This value is to be set by system engineers
+and please don't change it if not sure as this effects the failure
+detection mechanism using heartbeat.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="merge" a:defaultValue="200">
+ <a:documentation>This timeout specifies in milliseconds how
+long to wait before checking for a partition when no multicast traffic
+is being sent. If multicast traffic is being sent, the merge detection
+happens automatically as a function of the protocol.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="miss_count_const" a:defaultValue="5">
+ <a:documentation>This constant defines the maximum number
+of times on receipt of a token a message is checked for retransmission
+before a retransmission occurs. This parameter is useful to modify for
+switches that delay multicast packets compared to unicast packets.
+The default setting works well for nearly all modern switches.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="netmtu" a:defaultValue="1500">
+ <a:documentation>This specifies the network maximum transmit
+unit. To set this value beyond 1500, the regular frame MTU, requires
+ethernet devices that support large, or also called jumbo, frames.
+If any device in the network doesn't support large frames, the protocol
+will not operate properly. The hosts must also have their mtu size set
+from 1500 to whatever frame size is specified here.
+
+Please note while some NICs or switches
+claim large frame support, they support 9000 MTU as the maximum frame
+size including the IP header. Setting the *netmtu* and host MTUs to 9000
+will cause totem to use the full 9000 bytes of the frame. Then Linux
+will add a 18 byte header moving the full frame size to 9018.
+As a result some hardware will not operate properly with this size
+of data. A *netmtu* of 8982 seems to work for the few large frame devices
+that have been tested. Some manufacturers claim large frame support
+when in fact they support frame sizes of 4500 bytes.
+
+When sending multicast traffic, if the network frequently reconfigures,
+chances are that some device in the network doesn't support large frames.
+
+Choose hardware carefully if intending to use large frame support.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="nodeid">
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_autorecovery_check_timeout" a:defaultValue="1000">
+ <a:documentation>This specifies the time in milliseconds
+to check if the failed ring can be auto-recovered.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check: active,passive -> count(interface) <= 2
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="rrp_mode">
+ <a:documentation>This specifies the mode of redundant ring,
+which may be 'none', 'active', or 'passive'. Active replication offers
+none`, active, or passive. Active replication offers
+slightly lower latency from transmit to delivery in faulty network
+environments but with less performance. Passive replication may nearly
+double the speed of the totem protocol if the protocol doesn't become
+CPU bound. The final option is none, in which case only one network
+interface will be used to operate the totem protocol.
+
+If only one *interface* directive is specified, 'none' is automatically
+chosen. If multiple *interface* directives are specified, only 'active'
+or 'passive' may be chosen.
+
+The maximum number of *interface* directives that is allowed for either
+mode ('active' or 'passive') is 2.</a:documentation>
+ <choice>
+ <value>active</value>
+ <value>none</value>
+ <value>passive</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_problem_count_mcast_threshold">
+ <a:documentation>This specifies the number of times
+a problem is detected with multicast before setting the link faulty for
+passive RRP mode. This variable is unused in active RRP mode.
+
+The default is 10 times *rrp_problem_count_threshold*.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="rrp_problem_count_threshold" a:defaultValue="10">
+ <a:documentation>This specifies the number of times
+a problem is detected with a link before setting the link faulty.
+Once a link is set faulty, no more data is transmitted upon it. Also,
+the problem counter is no longer decremented when the problem count
+timeout expires.
+
+A problem is detected whenever all tokens from the proceeding
+processor have not been received within the *rrp_token_expired_timeout*.
+The *rrp_problem_count_threshold* * *rrp_token_expired_timeout* should be
+at least 50 milliseconds less then the *token* timeout, or a complete
+reconfiguration may occur.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_problem_count_timeout" a:defaultValue="2000">
+ <a:documentation>This specifies the time in milliseconds
+to wait before decrementing the problem count by 1 for a particular ring
+to ensure a link is not marked faulty for transient network failures.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="rrp_token_expired_timeout" a:defaultValue="47" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This specifies the time in milliseconds
+to increment the problem counter for the redundant ring protocol after
+not having received a token from all rings for a particular processor.
+
+This value will automatically be calculated from the *token* timeout
+and *problem_count_threshold* but may be overridden.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!--
+ XXX: implied check/migration to current items
+ CFG: corosync.conf, cluster.conf
+ -->
+ <attribute name="secauth" a:defaultValue="on" a4doc:deprecation-hint="It's recomended to use combination of *crypto_cipher* and *crypto_hash*.">
+ <a:documentation>This specifies that HMAC/SHA1 authentication should be used
+to authenticate all messages. It further specifies that all data
+should be encrypted with the nss library and aes256 encryption
+algorithm to protect data from eavesdropping.
+
+Enabling this option adds a encryption header to every message sent
+by totem which reduces total throughput. Also encryption and
+authentication consume extra CPU cycles in corosync.</a:documentation>
+ <choice>
+ <value>off</value>
+ <value>on</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="send_join" a:defaultValue="0" a4doc:danger-hint="Seek advice from the corosync mailing list if trying to run larger configurations.">
+ <a:documentation>This timeout specifies in milliseconds
+an upper range between 0 and *send_join* to wait before sending a join
+message. For eprecationtions with less then 32 nodes, this parameter
+is not necessary. For larger rings, this parameter is necessary
+to ensure the NIC is not overflowed with join messages on formation of
+a new ring. A reasonable value for large rings (128 nodes) would be
+80msec. Other timer values must also change if this value is changed.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="seqno_unchanged_const" a:defaultValue="30">
+ <a:documentation>This constant specifies how many rotations
+of the token without any multicast traffic should occur before the hold
+timer is started.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: undocumented -->
+ <attribute name="threads">
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="token" a:defaultValue="1000">
+ <a:documentation>This timeout specifies in milliseconds
+until a token loss is declared after not receiving a token. This is
+the time spent detecting a failure of a processor in the current
+configuration. Reforming a new configuration takes about 50
+milliseconds in addition to this timeout.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="token_retransmit" a:defaultValue="238" a4doc:danger-hint="It is not recommended to override this value without guidance from the corosync community.">
+ <a:documentation>This timeout specifies in milliseconds
+after how long before receiving a token the token is retransmitted.
+This will be automatically calculated if token is modified.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="token_retransmits_before_loss_const" a:defaultValue="4">
+ <a:documentation>This value identifies how many token
+retransmits should be attempted before forming a new configuration.
+If this value is set, retransmit and hold will be automatically
+calculated from *retransmits_before_loss* and token.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="transport" a:defaultValue="udp">
+ <choice>
+ <value>iba</value>
+ <value>udp</value>
+ <value>udpu</value>
+ </choice>
+ </attribute>
+ </optional>
+ <attribute name="version">
+ <a:documentation>This option controls the transport
+mechanism used. If the interface to which corosync is binding is
+an RDMA interface such as RoCEE or Infiniband, the 'iba' parameter
+may be specified. To avoid the use of multicast entirely, a unicast
+transport parameter 'udpu' can be specified. This requires specifying
+the list of members in *nodelist* directive, that could potentially make
+up the membership before deployment.</a:documentation>
+ <!-- CFG: corosync.conf -->
+ <a:documentation>This specifies the version of
+the configuration file. Currently the only valid value for this
+option is '2'.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="vsftype" a:defaultValue="ykd">
+ <a:documentation>This option controls the virtual
+synchrony filter type used to identify a primary component.
+The preferred choice is YKD dynamic linear voting, however, for
+clusters larger then 32 nodes YKD consumes alot of memory. For large
+scale clusters that are created by changing the MAX_PROCESSORS_COUNT
+#define in the C code totem.h file, the virtual synchrony filter 'none'
+is recommended but then AMF and DLCK services (which are currently
+experimental) are not safe for use.</a:documentation>
+ <choice>
+ <value>none</value>
+ <value>ykd</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="window_size" a:defaultValue="50">
+ <a:documentation>This constant specifies the maximum number
+of messages that may be sent on one token rotation. If all processors
+perform equally well, this value could be large (300), which would
+introduce higher latency from origination to delivery for very large
+rings. To reduce latency in large rings (16+), the defaults are a safe
+compromise. If 1 or more slow processor(s) are present among fast
+processors, *window_size* should be no larger then 256000 / *netmtu*
+to avoid overflow of the kernel receive buffers. The user is notified
+of this by the display of a retransmit list in the notification logs.
+There is no loss of data, but performance is reduced when these errors
+occur.</a:documentation>
+ <data type="unsignedInt"/>
+ </attribute>
+ </optional>
+ <zeroOrMore>
+ <ref name="interface"/>
+ </zeroOrMore>
+ </element>
+ </define>
+ <define name="interface">
+ <element name="interface">
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="bindnetaddr">
+ <a:documentation>This specifies the network address
+the corosync executive should bind to.
+*bindnetaddr* should be an IP address configured on the system, or
+a network address.
+
+For example, if the local interface is `192.168.5.92` with netmask
+`255.255.255.0`, you should set *bindnetaddr* to `192.168.5.92` or
+`192.168.5.0`. If the local interface is `192.168.5.92` with netmask
+`255.255.255.192`, set *bindnetaddr* to `192.168.5.92` or `192.168.5.64`,
+and so forth.
+
+This may also be an IPv6 address, in which case IPv6 networking will be
+used. In this case, the exact address must be specified and there is no
+automatic selection of the network interface within a specific subnet
+as with IPv4.
+
+If IPv6 networking is used, the *nodeid* field in *nodelist* must be
+specified.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf -->
+ <attribute name="broadcast" a:defaultValue="no">
+ <a:documentation>This is optional and can be set to 'yes'. If it is set to 'yes',
+the broadcast address will be used for communication. If this option
+is set, *mcastaddr* should not be set.</a:documentation>
+ <choice>
+ <value>no</value>
+ <value>yes</value>
+ </choice>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="mcastaddr">
+ <a:documentation>This is the multicast address used
+by corosync executive. The default should work for most networks, but
+the network administrator should be queried about a multicast address
+to use. Avoid `224.x.x.x` because this is a "config" multicast address.
+
+This may also be an IPv6 multicast address, in which case IPv6 networking
+will be used. If IPv6 networking is used, the *nodeid* field in
+*nodelist* must be specified.
+
+It's not needed to use this option if *cluster_name* option is used.
+If both options are used, *mcastaddr* has higher priority.</a:documentation>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="mcastport">
+ <a:documentation>This specifies the UDP port number.
+It is possible to use the same multicast address on a network with
+the corosync services configured for different UDP ports. Please note
+corosync uses two UDP ports *mcastport* (for mcast receives) and
+*mcastport* - 1 (for mcast sends). If you have multiple clusters
+on the same network using the same *mcastaddr* please configure
+the **mcastport**s with a gap.</a:documentation>
+ <data type="unsignedShort"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ringnumber">
+ <a:documentation>This specifies the ring number for
+the interface. When using the redundant ring protocol, each interface
+should specify separate ring numbers to uniquely identify to
+the membership protocol which interface to use for which redundant ring.
+The *ringnumber* must start at 0.</a:documentation>
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ <optional>
+ <!-- CFG: corosync.conf, cluster.conf -->
+ <attribute name="ttl" a:defaultValue="1">
+ <a:documentation>This specifies the Time To Live (TTL).
+If you run your cluster on a routed network then the default of '1' will
+be too small. This option provides a way to increase this up to '255'.
+The valid range is '0..255'. Note that this is only valid on multicast
+transport types.</a:documentation>
+ <data type="unsignedByte"/>
+ </attribute>
+ </optional>
+ </element>
+ </define>
+</grammar>
+<!--
+ uidgid =
+ element uidgid {
+ attribute uid {text}?,
+ attribute gid {text}?
+ }
+-->
diff --git a/lex.py b/lex.py
index 833213c..137d28f 100644
--- a/lex.py
+++ b/lex.py
@@ -1,289 +1,313 @@
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# ply: lex.py
#
-# Author: David M. Beazley (beazley@cs.uchicago.edu)
-# Department of Computer Science
-# University of Chicago
-# Chicago, IL 60637
-#
-# Copyright (C) 2001, David M. Beazley
-#
-# $Header: /cvsroot/ply/ply/lex.py,v 1.7 2002/12/12 21:16:33 beazley Exp $
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-# See the file COPYING for a complete copy of the LGPL.
+# Copyright (C) 2001-2011,
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
#
-# This module automatically constructs a lexical analysis module from regular
-# expression rules defined in a user-defined module. The idea is essentially the same
-# as that used in John Aycock's Spark framework, but the implementation works
-# at the module level rather than requiring the use of classes.
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
#
-# This module tries to provide an interface that is closely modeled after
-# the traditional lex interface in Unix. It also differs from Spark
-# in that:
-#
-# - It provides more extensive error checking and reporting if
-# the user supplies a set of regular expressions that can't
-# be compiled or if there is any other kind of a problem in
-# the specification.
-#
-# - The interface is geared towards LALR(1) and LR(1) parser
-# generators. That is tokens are generated one at a time
-# rather than being generated in advanced all in one step.
-#
-# There are a few limitations of this module
-#
-# - The module interface makes it somewhat awkward to support more
-# than one lexer at a time. Although somewhat inelegant from a
-# design perspective, this is rarely a practical concern for
-# most compiler projects.
-#
-# - The lexer requires that the entire input text be read into
-# a string before scanning. I suppose that most machines have
-# enough memory to make this a minor issues, but it makes
-# the lexer somewhat difficult to use in interactive sessions
-# or with streaming data.
-#
-#-----------------------------------------------------------------------------
-
-r"""
-lex.py
-
-This module builds lex-like scanners based on regular expression rules.
-To use the module, simply write a collection of regular expression rules
-and actions like this:
-
-# lexer.py
-import lex
-
-# Define a list of valid tokens
-tokens = (
- 'IDENTIFIER', 'NUMBER', 'PLUS', 'MINUS'
- )
-
-# Define tokens as functions
-def t_IDENTIFIER(t):
- r' ([a-zA-Z_](\w|_)* '
- return t
-
-def t_NUMBER(t):
- r' \d+ '
- return t
-
-# Some simple tokens with no actions
-t_PLUS = r'\+'
-t_MINUS = r'-'
-
-# Initialize the lexer
-lex.lex()
-
-The tokens list is required and contains a complete list of all valid
-token types that the lexer is allowed to produce. Token types are
-restricted to be valid identifiers. This means that 'MINUS' is a valid
-token type whereas '-' is not.
-
-Rules are defined by writing a function with a name of the form
-t_rulename. Each rule must accept a single argument which is
-a token object generated by the lexer. This token has the following
-attributes:
-
- t.type = type string of the token. This is initially set to the
- name of the rule without the leading t_
- t.value = The value of the lexeme.
- t.lineno = The value of the line number where the token was encountered
-
-For example, the t_NUMBER() rule above might be called with the following:
-
- t.type = 'NUMBER'
- t.value = '42'
- t.lineno = 3
-
-Each rule returns the token object it would like to supply to the
-parser. In most cases, the token t is returned with few, if any
-modifications. To discard a token for things like whitespace or
-comments, simply return nothing. For instance:
-
-def t_whitespace(t):
- r' \s+ '
- pass
-
-For faster lexing, you can also define this in terms of the ignore set like this:
-
-t_ignore = ' \t'
-
-The characters in this string are ignored by the lexer. Use of this feature can speed
-up parsing significantly since scanning will immediately proceed to the next token.
-
-lex requires that the token returned by each rule has an attribute
-t.type. Other than this, rules are free to return any kind of token
-object that they wish and may construct a new type of token object
-from the attributes of t (provided the new object has the required
-type attribute).
-
-If illegal characters are encountered, the scanner executes the
-function t_error(t) where t is a token representing the rest of the
-string that hasn't been matched. If this function isn't defined, a
-LexError exception is raised. The .text attribute of this exception
-object contains the part of the string that wasn't matched.
-
-The t.skip(n) method can be used to skip ahead n characters in the
-input stream. This is usually only used in the error handling rule.
-For instance, the following rule would print an error message and
-continue:
-
-def t_error(t):
- print "Illegal character in input %s" % t.value[0]
- t.skip(1)
-
-Of course, a nice scanner might wish to skip more than one character
-if the input looks very corrupted.
-
-The lex module defines a t.lineno attribute on each token that can be used
-to track the current line number in the input. The value of this
-variable is not modified by lex so it is up to your lexer module
-to correctly update its value depending on the lexical properties
-of the input language. To do this, you might write rules such as
-the following:
-
-def t_newline(t):
- r' \n+ '
- t.lineno += t.value.count("\n")
-
-To initialize your lexer so that it can be used, simply call the lex.lex()
-function in your rule file. If there are any errors in your
-specification, warning messages or an exception will be generated to
-alert you to the problem.
-
-(dave: this needs to be rewritten)
-To use the newly constructed lexer from another module, simply do
-this:
-
- import lex
- import lexer
- plex.input("position = initial + rate*60")
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
- while 1:
- token = plex.token() # Get a token
- if not token: break # No more tokens
- ... do whatever ...
+__version__ = "3.5"
+__tabversion__ = "3.5" # Version of table file used
-Assuming that the module 'lexer' has initialized plex as shown
-above, parsing modules can safely import 'plex' without having
-to import the rule file or any additional imformation about the
-scanner you have defined.
-"""
+import re, sys, types, copy, os
-# -----------------------------------------------------------------------------
+# This tuple contains known string types
+try:
+ # Python 2.6
+ StringTypes = (types.StringType, types.UnicodeType)
+except AttributeError:
+ # Python 3.0
+ StringTypes = (str, bytes)
+# Extract the code attribute of a function. Different implementations
+# are for Python 2/3 compatibility.
-__version__ = "1.3"
+if sys.version_info[0] < 3:
+ def func_code(f):
+ return f.func_code
+else:
+ def func_code(f):
+ return f.__code__
-import re, types, sys, copy
+# This regular expression is used to match valid token names
+_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')
+
+# Exception thrown when invalid token encountered and no default error
+# handler is defined.
-# Exception thrown when invalid token encountered and no default
class LexError(Exception):
def __init__(self,message,s):
self.args = (message,)
self.text = s
-# Token class
-class LexToken:
+# Token class. This class is used to represent the tokens produced.
+class LexToken(object):
def __str__(self):
- return "LexToken(%s,%r,%d)" % (self.type,self.value,self.lineno)
+ return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos)
def __repr__(self):
return str(self)
- def skip(self,n):
- try:
- self._skipn += n
- except AttributeError:
- self._skipn = n
+
+# This object is a stand-in for a logging object created by the
+# logging module.
+
+class PlyLogger(object):
+ def __init__(self,f):
+ self.f = f
+ def critical(self,msg,*args,**kwargs):
+ self.f.write((msg % args) + "\n")
+
+ def warning(self,msg,*args,**kwargs):
+ self.f.write("WARNING: "+ (msg % args) + "\n")
+
+ def error(self,msg,*args,**kwargs):
+ self.f.write("ERROR: " + (msg % args) + "\n")
+
+ info = critical
+ debug = critical
+
+# Null logger is used when no output is generated. Does nothing.
+class NullLogger(object):
+ def __getattribute__(self,name):
+ return self
+ def __call__(self,*args,**kwargs):
+ return self
# -----------------------------------------------------------------------------
-# Lexer class
+# === Lexing Engine ===
+#
+# The following Lexer class implements the lexer runtime. There are only
+# a few public methods and attributes:
#
# input() - Store a new string in the lexer
# token() - Get the next token
+# clone() - Clone the lexer
+#
+# lineno - Current line number
+# lexpos - Current position in the input string
# -----------------------------------------------------------------------------
class Lexer:
def __init__(self):
- self.lexre = None # Master regular expression
- self.lexdata = None # Actual input data (as a string)
- self.lexpos = 0 # Current position in input text
- self.lexlen = 0 # Length of the input text
- self.lexindexfunc = [ ] # Reverse mapping of groups to functions and types
- self.lexerrorf = None # Error rule (if any)
- self.lextokens = None # List of valid tokens
- self.lexignore = None # Ignored characters
- self.lineno = 1 # Current line number
- self.debug = 0 # Debugging mode
- self.optimize = 0 # Optimized mode
- self.token = self.errtoken
-
- def __copy__(self):
- c = Lexer()
- c.lexre = self.lexre
- c.lexdata = self.lexdata
- c.lexpos = self.lexpos
- c.lexlen = self.lexlen
- c.lenindexfunc = self.lexindexfunc
- c.lexerrorf = self.lexerrorf
- c.lextokens = self.lextokens
- c.lexignore = self.lexignore
- c.lineno = self.lineno
- c.optimize = self.optimize
- c.token = c.realtoken
+ self.lexre = None # Master regular expression. This is a list of
+ # tuples (re,findex) where re is a compiled
+ # regular expression and findex is a list
+ # mapping regex group numbers to rules
+ self.lexretext = None # Current regular expression strings
+ self.lexstatere = {} # Dictionary mapping lexer states to master regexs
+ self.lexstateretext = {} # Dictionary mapping lexer states to regex strings
+ self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names
+ self.lexstate = "INITIAL" # Current lexer state
+ self.lexstatestack = [] # Stack of lexer states
+ self.lexstateinfo = None # State information
+ self.lexstateignore = {} # Dictionary of ignored characters for each state
+ self.lexstateerrorf = {} # Dictionary of error functions for each state
+ self.lexreflags = 0 # Optional re compile flags
+ self.lexdata = None # Actual input data (as a string)
+ self.lexpos = 0 # Current position in input text
+ self.lexlen = 0 # Length of the input text
+ self.lexerrorf = None # Error rule (if any)
+ self.lextokens = None # List of valid tokens
+ self.lexignore = "" # Ignored characters
+ self.lexliterals = "" # Literal characters that can be passed through
+ self.lexmodule = None # Module
+ self.lineno = 1 # Current line number
+ self.lexoptimize = 0 # Optimized mode
+
+ def clone(self,object=None):
+ c = copy.copy(self)
+
+ # If the object parameter has been supplied, it means we are attaching the
+ # lexer to a new object. In this case, we have to rebind all methods in
+ # the lexstatere and lexstateerrorf tables.
+
+ if object:
+ newtab = { }
+ for key, ritem in self.lexstatere.items():
+ newre = []
+ for cre, findex in ritem:
+ newfindex = []
+ for f in findex:
+ if not f or not f[0]:
+ newfindex.append(f)
+ continue
+ newfindex.append((getattr(object,f[0].__name__),f[1]))
+ newre.append((cre,newfindex))
+ newtab[key] = newre
+ c.lexstatere = newtab
+ c.lexstateerrorf = { }
+ for key, ef in self.lexstateerrorf.items():
+ c.lexstateerrorf[key] = getattr(object,ef.__name__)
+ c.lexmodule = object
+ return c
+
+ # ------------------------------------------------------------
+ # writetab() - Write lexer information to a table file
+ # ------------------------------------------------------------
+ def writetab(self,tabfile,outputdir=""):
+ if isinstance(tabfile,types.ModuleType):
+ return
+ basetabfilename = tabfile.split(".")[-1]
+ filename = os.path.join(outputdir,basetabfilename)+".py"
+ tf = open(filename,"w")
+ tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__))
+ tf.write("_tabversion = %s\n" % repr(__tabversion__))
+ tf.write("_lextokens = %s\n" % repr(self.lextokens))
+ tf.write("_lexreflags = %s\n" % repr(self.lexreflags))
+ tf.write("_lexliterals = %s\n" % repr(self.lexliterals))
+ tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo))
+
+ tabre = { }
+ # Collect all functions in the initial state
+ initial = self.lexstatere["INITIAL"]
+ initialfuncs = []
+ for part in initial:
+ for f in part[1]:
+ if f and f[0]:
+ initialfuncs.append(f)
+
+ for key, lre in self.lexstatere.items():
+ titem = []
+ for i in range(len(lre)):
+ titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i])))
+ tabre[key] = titem
+
+ tf.write("_lexstatere = %s\n" % repr(tabre))
+ tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore))
+
+ taberr = { }
+ for key, ef in self.lexstateerrorf.items():
+ if ef:
+ taberr[key] = ef.__name__
+ else:
+ taberr[key] = None
+ tf.write("_lexstateerrorf = %s\n" % repr(taberr))
+ tf.close()
+
+ # ------------------------------------------------------------
+ # readtab() - Read lexer information from a tab file
+ # ------------------------------------------------------------
+ def readtab(self,tabfile,fdict):
+ if isinstance(tabfile,types.ModuleType):
+ lextab = tabfile
+ else:
+ if sys.version_info[0] < 3:
+ exec("import %s as lextab" % tabfile)
+ else:
+ env = { }
+ exec("import %s as lextab" % tabfile, env,env)
+ lextab = env['lextab']
+
+ if getattr(lextab,"_tabversion","0.0") != __tabversion__:
+ raise ImportError("Inconsistent PLY version")
+
+ self.lextokens = lextab._lextokens
+ self.lexreflags = lextab._lexreflags
+ self.lexliterals = lextab._lexliterals
+ self.lexstateinfo = lextab._lexstateinfo
+ self.lexstateignore = lextab._lexstateignore
+ self.lexstatere = { }
+ self.lexstateretext = { }
+ for key,lre in lextab._lexstatere.items():
+ titem = []
+ txtitem = []
+ for i in range(len(lre)):
+ titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict)))
+ txtitem.append(lre[i][0])
+ self.lexstatere[key] = titem
+ self.lexstateretext[key] = txtitem
+ self.lexstateerrorf = { }
+ for key,ef in lextab._lexstateerrorf.items():
+ self.lexstateerrorf[key] = fdict[ef]
+ self.begin('INITIAL')
# ------------------------------------------------------------
# input() - Push a new string into the lexer
# ------------------------------------------------------------
def input(self,s):
- if not isinstance(s,types.StringType):
- raise ValueError, "Expected a string"
+ # Pull off the first character to see if s looks like a string
+ c = s[:1]
+ if not isinstance(c,StringTypes):
+ raise ValueError("Expected a string")
self.lexdata = s
self.lexpos = 0
self.lexlen = len(s)
- self.token = self.realtoken
-
- # Change the token routine to point to realtoken()
- global token
- if token == self.errtoken:
- token = self.token
# ------------------------------------------------------------
- # errtoken() - Return error if token is called with no data
+ # begin() - Changes the lexing state
+ # ------------------------------------------------------------
+ def begin(self,state):
+ if not state in self.lexstatere:
+ raise ValueError("Undefined state")
+ self.lexre = self.lexstatere[state]
+ self.lexretext = self.lexstateretext[state]
+ self.lexignore = self.lexstateignore.get(state,"")
+ self.lexerrorf = self.lexstateerrorf.get(state,None)
+ self.lexstate = state
+
# ------------------------------------------------------------
- def errtoken(self):
- raise RuntimeError, "No input string given with input()"
-
+ # push_state() - Changes the lexing state and saves old on stack
# ------------------------------------------------------------
- # token() - Return the next token from the Lexer
+ def push_state(self,state):
+ self.lexstatestack.append(self.lexstate)
+ self.begin(state)
+
+ # ------------------------------------------------------------
+ # pop_state() - Restores the previous state
+ # ------------------------------------------------------------
+ def pop_state(self):
+ self.begin(self.lexstatestack.pop())
+
+ # ------------------------------------------------------------
+ # current_state() - Returns the current lexing state
+ # ------------------------------------------------------------
+ def current_state(self):
+ return self.lexstate
+
+ # ------------------------------------------------------------
+ # skip() - Skip ahead n characters
+ # ------------------------------------------------------------
+ def skip(self,n):
+ self.lexpos += n
+
+ # ------------------------------------------------------------
+ # opttoken() - Return the next token from the Lexer
#
# Note: This function has been carefully implemented to be as fast
# as possible. Don't make changes unless you really know what
# you are doing
# ------------------------------------------------------------
- def realtoken(self):
+ def token(self):
# Make local copies of frequently referenced attributes
lexpos = self.lexpos
lexlen = self.lexlen
lexignore = self.lexignore
lexdata = self.lexdata
-
+
while lexpos < lexlen:
# This code provides some short-circuit code for whitespace, tabs, and other ignored characters
if lexdata[lexpos] in lexignore:
@@ -291,361 +315,709 @@ class Lexer:
continue
# Look for a regular expression match
- m = self.lexre.match(lexdata,lexpos)
- if m:
- i = m.lastindex
- lexpos = m.end()
+ for lexre,lexindexfunc in self.lexre:
+ m = lexre.match(lexdata,lexpos)
+ if not m: continue
+
+ # Create a token for return
tok = LexToken()
tok.value = m.group()
tok.lineno = self.lineno
- tok.lexer = self
- func,tok.type = self.lexindexfunc[i]
+ tok.lexpos = lexpos
+
+ i = m.lastindex
+ func,tok.type = lexindexfunc[i]
+
if not func:
- self.lexpos = lexpos
- return tok
-
+ # If no token type was set, it's an ignored token
+ if tok.type:
+ self.lexpos = m.end()
+ return tok
+ else:
+ lexpos = m.end()
+ break
+
+ lexpos = m.end()
+
# If token is processed by a function, call it
+
+ tok.lexer = self # Set additional attributes useful in token rules
+ self.lexmatch = m
self.lexpos = lexpos
+
newtok = func(tok)
- self.lineno = tok.lineno # Update line number
-
+
# Every function must return a token, if nothing, we just move to next token
- if not newtok: continue
-
+ if not newtok:
+ lexpos = self.lexpos # This is here in case user has updated lexpos.
+ lexignore = self.lexignore # This is here in case there was a state change
+ break
+
# Verify type of the token. If not in the token map, raise an error
- if not self.optimize:
- if not self.lextokens.has_key(newtok.type):
- raise LexError, ("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
- func.func_code.co_filename, func.func_code.co_firstlineno,
+ if not self.lexoptimize:
+ if not newtok.type in self.lextokens:
+ raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
+ func_code(func).co_filename, func_code(func).co_firstlineno,
func.__name__, newtok.type),lexdata[lexpos:])
return newtok
+ else:
+ # No match, see if in literals
+ if lexdata[lexpos] in self.lexliterals:
+ tok = LexToken()
+ tok.value = lexdata[lexpos]
+ tok.lineno = self.lineno
+ tok.type = tok.value
+ tok.lexpos = lexpos
+ self.lexpos = lexpos + 1
+ return tok
- # No match. Call t_error() if defined.
- if self.lexerrorf:
- tok = LexToken()
- tok.value = self.lexdata[lexpos:]
- tok.lineno = self.lineno
- tok.type = "error"
- tok.lexer = self
- oldpos = lexpos
- newtok = self.lexerrorf(tok)
- lexpos += getattr(tok,"_skipn",0)
- if oldpos == lexpos:
- # Error method didn't change text position at all. This is an error.
+ # No match. Call t_error() if defined.
+ if self.lexerrorf:
+ tok = LexToken()
+ tok.value = self.lexdata[lexpos:]
+ tok.lineno = self.lineno
+ tok.type = "error"
+ tok.lexer = self
+ tok.lexpos = lexpos
self.lexpos = lexpos
- raise LexError, ("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
- if not newtok: continue
- self.lexpos = lexpos
- return newtok
+ newtok = self.lexerrorf(tok)
+ if lexpos == self.lexpos:
+ # Error method didn't change text position at all. This is an error.
+ raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
+ lexpos = self.lexpos
+ if not newtok: continue
+ return newtok
- self.lexpos = lexpos
- raise LexError, ("No match found", lexdata[lexpos:])
+ self.lexpos = lexpos
+ raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:])
- # No more input data
self.lexpos = lexpos + 1
+ if self.lexdata is None:
+ raise RuntimeError("No input string given with input()")
return None
-
+ # Iterator interface
+ def __iter__(self):
+ return self
+
+ def next(self):
+ t = self.token()
+ if t is None:
+ raise StopIteration
+ return t
+
+ __next__ = next
+
+# -----------------------------------------------------------------------------
+# ==== Lex Builder ===
+#
+# The functions and classes below are used to collect lexing information
+# and build a Lexer object from it.
+# -----------------------------------------------------------------------------
+
# -----------------------------------------------------------------------------
-# validate_file()
+# _get_regex(func)
#
-# This checks to see if there are duplicated t_rulename() functions or strings
-# in the parser input file. This is done using a simple regular expression
-# match on each line in the filename.
+# Returns the regular expression assigned to a function either as a doc string
+# or as a .regex attribute attached by the @TOKEN decorator.
# -----------------------------------------------------------------------------
-def validate_file(filename):
- import os.path
- base,ext = os.path.splitext(filename)
- if ext != '.py': return 1 # No idea what the file is. Return OK
+def _get_regex(func):
+ return getattr(func,"regex",func.__doc__)
+# -----------------------------------------------------------------------------
+# get_caller_module_dict()
+#
+# This function returns a dictionary containing all of the symbols defined within
+# a caller further down the call stack. This is used to get the environment
+# associated with the yacc() call if none was provided.
+# -----------------------------------------------------------------------------
+
+def get_caller_module_dict(levels):
try:
- f = open(filename)
- lines = f.readlines()
- f.close()
- except IOError:
- return 1 # Oh well
-
- fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
- sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
- counthash = { }
- linen = 1
- noerror = 1
- for l in lines:
- m = fre.match(l)
- if not m:
- m = sre.match(l)
- if m:
- name = m.group(1)
- prev = counthash.get(name)
- if not prev:
- counthash[name] = linen
- else:
- print "%s:%d: Rule %s redefined. Previously defined on line %d" % (filename,linen,name,prev)
- noerror = 0
- linen += 1
- return noerror
+ raise RuntimeError
+ except RuntimeError:
+ e,b,t = sys.exc_info()
+ f = t.tb_frame
+ while levels > 0:
+ f = f.f_back
+ levels -= 1
+ ldict = f.f_globals.copy()
+ if f.f_globals != f.f_locals:
+ ldict.update(f.f_locals)
+
+ return ldict
+
+# -----------------------------------------------------------------------------
+# _funcs_to_names()
+#
+# Given a list of regular expression functions, this converts it to a list
+# suitable for output to a table file
+# -----------------------------------------------------------------------------
+
+def _funcs_to_names(funclist,namelist):
+ result = []
+ for f,name in zip(funclist,namelist):
+ if f and f[0]:
+ result.append((name, f[1]))
+ else:
+ result.append(f)
+ return result
+
+# -----------------------------------------------------------------------------
+# _names_to_funcs()
+#
+# Given a list of regular expression function names, this converts it back to
+# functions.
+# -----------------------------------------------------------------------------
+
+def _names_to_funcs(namelist,fdict):
+ result = []
+ for n in namelist:
+ if n and n[0]:
+ result.append((fdict[n[0]],n[1]))
+ else:
+ result.append(n)
+ return result
+
+# -----------------------------------------------------------------------------
+# _form_master_re()
+#
+# This function takes a list of all of the regex components and attempts to
+# form the master regular expression. Given limitations in the Python re
+# module, it may be necessary to break the master regex into separate expressions.
+# -----------------------------------------------------------------------------
+
+def _form_master_re(relist,reflags,ldict,toknames):
+ if not relist: return []
+ regex = "|".join(relist)
+ try:
+ lexre = re.compile(regex,re.VERBOSE | reflags)
+
+ # Build the index to function map for the matching engine
+ lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1)
+ lexindexnames = lexindexfunc[:]
+
+ for f,i in lexre.groupindex.items():
+ handle = ldict.get(f,None)
+ if type(handle) in (types.FunctionType, types.MethodType):
+ lexindexfunc[i] = (handle,toknames[f])
+ lexindexnames[i] = f
+ elif handle is not None:
+ lexindexnames[i] = f
+ if f.find("ignore_") > 0:
+ lexindexfunc[i] = (None,None)
+ else:
+ lexindexfunc[i] = (None, toknames[f])
+
+ return [(lexre,lexindexfunc)],[regex],[lexindexnames]
+ except Exception:
+ m = int(len(relist)/2)
+ if m == 0: m = 1
+ llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames)
+ rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames)
+ return llist+rlist, lre+rre, lnames+rnames
+
+# -----------------------------------------------------------------------------
+# def _statetoken(s,names)
+#
+# Given a declaration name s of the form "t_" and a dictionary whose keys are
+# state names, this function returns a tuple (states,tokenname) where states
+# is a tuple of state names and tokenname is the name of the token. For example,
+# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM')
+# -----------------------------------------------------------------------------
+
+def _statetoken(s,names):
+ nonstate = 1
+ parts = s.split("_")
+ for i in range(1,len(parts)):
+ if not parts[i] in names and parts[i] != 'ANY': break
+ if i > 1:
+ states = tuple(parts[1:i])
+ else:
+ states = ('INITIAL',)
+
+ if 'ANY' in states:
+ states = tuple(names)
+
+ tokenname = "_".join(parts[i:])
+ return (states,tokenname)
+
# -----------------------------------------------------------------------------
-# _read_lextab(module)
+# LexerReflect()
#
-# Reads lexer table from a lextab file instead of using introspection.
+# This class represents information needed to build a lexer as extracted from a
+# user's input file.
# -----------------------------------------------------------------------------
+class LexerReflect(object):
+ def __init__(self,ldict,log=None,reflags=0):
+ self.ldict = ldict
+ self.error_func = None
+ self.tokens = []
+ self.reflags = reflags
+ self.stateinfo = { 'INITIAL' : 'inclusive'}
+ self.files = {}
+ self.error = 0
+
+ if log is None:
+ self.log = PlyLogger(sys.stderr)
+ else:
+ self.log = log
+
+ # Get all of the basic information
+ def get_all(self):
+ self.get_tokens()
+ self.get_literals()
+ self.get_states()
+ self.get_rules()
+
+ # Validate all of the information
+ def validate_all(self):
+ self.validate_tokens()
+ self.validate_literals()
+ self.validate_rules()
+ return self.error
-def _read_lextab(lexer, fdict, module):
- exec "import %s as lextab" % module
- lexer.lexre = re.compile(lextab._lexre, re.VERBOSE)
- lexer.lexindexfunc = lextab._lextab
- for i in range(len(lextab._lextab)):
- t = lexer.lexindexfunc[i]
- if t:
- if t[0]:
- lexer.lexindexfunc[i] = (fdict[t[0]],t[1])
- lexer.lextokens = lextab._lextokens
- lexer.lexignore = lextab._lexignore
- if lextab._lexerrorf:
- lexer.lexerrorf = fdict[lextab._lexerrorf]
+ # Get the tokens map
+ def get_tokens(self):
+ tokens = self.ldict.get("tokens",None)
+ if not tokens:
+ self.log.error("No token list is defined")
+ self.error = 1
+ return
+
+ if not isinstance(tokens,(list, tuple)):
+ self.log.error("tokens must be a list or tuple")
+ self.error = 1
+ return
+ if not tokens:
+ self.log.error("tokens is empty")
+ self.error = 1
+ return
+
+ self.tokens = tokens
+
+ # Validate the tokens
+ def validate_tokens(self):
+ terminals = {}
+ for n in self.tokens:
+ if not _is_identifier.match(n):
+ self.log.error("Bad token name '%s'",n)
+ self.error = 1
+ if n in terminals:
+ self.log.warning("Token '%s' multiply defined", n)
+ terminals[n] = 1
+
+ # Get the literals specifier
+ def get_literals(self):
+ self.literals = self.ldict.get("literals","")
+ if not self.literals:
+ self.literals = ""
+
+ # Validate literals
+ def validate_literals(self):
+ try:
+ for c in self.literals:
+ if not isinstance(c,StringTypes) or len(c) > 1:
+ self.log.error("Invalid literal %s. Must be a single character", repr(c))
+ self.error = 1
+
+ except TypeError:
+ self.log.error("Invalid literals specification. literals must be a sequence of characters")
+ self.error = 1
+
+ def get_states(self):
+ self.states = self.ldict.get("states",None)
+ # Build statemap
+ if self.states:
+ if not isinstance(self.states,(tuple,list)):
+ self.log.error("states must be defined as a tuple or list")
+ self.error = 1
+ else:
+ for s in self.states:
+ if not isinstance(s,tuple) or len(s) != 2:
+ self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s))
+ self.error = 1
+ continue
+ name, statetype = s
+ if not isinstance(name,StringTypes):
+ self.log.error("State name %s must be a string", repr(name))
+ self.error = 1
+ continue
+ if not (statetype == 'inclusive' or statetype == 'exclusive'):
+ self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name)
+ self.error = 1
+ continue
+ if name in self.stateinfo:
+ self.log.error("State '%s' already defined",name)
+ self.error = 1
+ continue
+ self.stateinfo[name] = statetype
+
+ # Get all of the symbols with a t_ prefix and sort them into various
+ # categories (functions, strings, error functions, and ignore characters)
+
+ def get_rules(self):
+ tsymbols = [f for f in self.ldict if f[:2] == 't_' ]
+
+ # Now build up a list of functions and a list of strings
+
+ self.toknames = { } # Mapping of symbols to token names
+ self.funcsym = { } # Symbols defined as functions
+ self.strsym = { } # Symbols defined as strings
+ self.ignore = { } # Ignore strings by state
+ self.errorf = { } # Error functions by state
+
+ for s in self.stateinfo:
+ self.funcsym[s] = []
+ self.strsym[s] = []
+
+ if len(tsymbols) == 0:
+ self.log.error("No rules of the form t_rulename are defined")
+ self.error = 1
+ return
+
+ for f in tsymbols:
+ t = self.ldict[f]
+ states, tokname = _statetoken(f,self.stateinfo)
+ self.toknames[f] = tokname
+
+ if hasattr(t,"__call__"):
+ if tokname == 'error':
+ for s in states:
+ self.errorf[s] = t
+ elif tokname == 'ignore':
+ line = func_code(t).co_firstlineno
+ file = func_code(t).co_filename
+ self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__)
+ self.error = 1
+ else:
+ for s in states:
+ self.funcsym[s].append((f,t))
+ elif isinstance(t, StringTypes):
+ if tokname == 'ignore':
+ for s in states:
+ self.ignore[s] = t
+ if "\\" in t:
+ self.log.warning("%s contains a literal backslash '\\'",f)
+
+ elif tokname == 'error':
+ self.log.error("Rule '%s' must be defined as a function", f)
+ self.error = 1
+ else:
+ for s in states:
+ self.strsym[s].append((f,t))
+ else:
+ self.log.error("%s not defined as a function or string", f)
+ self.error = 1
+
+ # Sort the functions by line number
+ for f in self.funcsym.values():
+ if sys.version_info[0] < 3:
+ f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno))
+ else:
+ # Python 3.0
+ f.sort(key=lambda x: func_code(x[1]).co_firstlineno)
+
+ # Sort the strings by regular expression length
+ for s in self.strsym.values():
+ if sys.version_info[0] < 3:
+ s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1])))
+ else:
+ # Python 3.0
+ s.sort(key=lambda x: len(x[1]),reverse=True)
+
+ # Validate all of the t_rules collected
+ def validate_rules(self):
+ for state in self.stateinfo:
+ # Validate all rules defined by functions
+
+
+
+ for fname, f in self.funcsym[state]:
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ self.files[file] = 1
+
+ tokname = self.toknames[fname]
+ if isinstance(f, types.MethodType):
+ reqargs = 2
+ else:
+ reqargs = 1
+ nargs = func_code(f).co_argcount
+ if nargs > reqargs:
+ self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+ self.error = 1
+ continue
+
+ if nargs < reqargs:
+ self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+ self.error = 1
+ continue
+
+ if not _get_regex(f):
+ self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__)
+ self.error = 1
+ continue
+
+ try:
+ c = re.compile("(?P<%s>%s)" % (fname, _get_regex(f)), re.VERBOSE | self.reflags)
+ if c.match(""):
+ self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__)
+ self.error = 1
+ except re.error:
+ _etype, e, _etrace = sys.exc_info()
+ self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e)
+ if '#' in _get_regex(f):
+ self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__)
+ self.error = 1
+
+ # Validate all rules defined by strings
+ for name,r in self.strsym[state]:
+ tokname = self.toknames[name]
+ if tokname == 'error':
+ self.log.error("Rule '%s' must be defined as a function", name)
+ self.error = 1
+ continue
+
+ if not tokname in self.tokens and tokname.find("ignore_") < 0:
+ self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname)
+ self.error = 1
+ continue
+
+ try:
+ c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags)
+ if (c.match("")):
+ self.log.error("Regular expression for rule '%s' matches empty string",name)
+ self.error = 1
+ except re.error:
+ _etype, e, _etrace = sys.exc_info()
+ self.log.error("Invalid regular expression for rule '%s'. %s",name,e)
+ if '#' in r:
+ self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name)
+ self.error = 1
+
+ if not self.funcsym[state] and not self.strsym[state]:
+ self.log.error("No rules defined for state '%s'",state)
+ self.error = 1
+
+ # Validate the error function
+ efunc = self.errorf.get(state,None)
+ if efunc:
+ f = efunc
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ self.files[file] = 1
+
+ if isinstance(f, types.MethodType):
+ reqargs = 2
+ else:
+ reqargs = 1
+ nargs = func_code(f).co_argcount
+ if nargs > reqargs:
+ self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+ self.error = 1
+
+ if nargs < reqargs:
+ self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+ self.error = 1
+
+ for f in self.files:
+ self.validate_file(f)
+
+
+ # -----------------------------------------------------------------------------
+ # validate_file()
+ #
+ # This checks to see if there are duplicated t_rulename() functions or strings
+ # in the parser input file. This is done using a simple regular expression
+ # match on each line in the given file.
+ # -----------------------------------------------------------------------------
+
+ def validate_file(self,filename):
+ import os.path
+ base,ext = os.path.splitext(filename)
+ if ext != '.py': return # No idea what the file is. Return OK
+
+ try:
+ f = open(filename)
+ lines = f.readlines()
+ f.close()
+ except IOError:
+ return # Couldn't find the file. Don't worry about it
+
+ fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
+ sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
+
+ counthash = { }
+ linen = 1
+ for l in lines:
+ m = fre.match(l)
+ if not m:
+ m = sre.match(l)
+ if m:
+ name = m.group(1)
+ prev = counthash.get(name)
+ if not prev:
+ counthash[name] = linen
+ else:
+ self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev)
+ self.error = 1
+ linen += 1
+
# -----------------------------------------------------------------------------
# lex(module)
#
# Build all of the regular expression rules from definitions in the supplied module
# -----------------------------------------------------------------------------
-def lex(module=None,debug=0,optimize=0,lextab="lextab"):
+def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None):
+ global lexer
ldict = None
- regex = ""
- error = 0
- files = { }
- lexer = Lexer()
- lexer.debug = debug
- lexer.optimize = optimize
+ stateinfo = { 'INITIAL' : 'inclusive'}
+ lexobj = Lexer()
+ lexobj.lexoptimize = optimize
global token,input
-
+
+ if errorlog is None:
+ errorlog = PlyLogger(sys.stderr)
+
+ if debug:
+ if debuglog is None:
+ debuglog = PlyLogger(sys.stderr)
+
+ # Get the module dictionary used for the lexer
+ if object: module = object
+
if module:
- if not isinstance(module, types.ModuleType):
- raise ValueError,"Expected a module"
-
- ldict = module.__dict__
-
+ _items = [(k,getattr(module,k)) for k in dir(module)]
+ ldict = dict(_items)
else:
- # No module given. We might be able to get information from the caller.
- try:
- raise RuntimeError
- except RuntimeError:
- e,b,t = sys.exc_info()
- f = t.tb_frame
- f = f.f_back # Walk out to our calling function
- ldict = f.f_globals # Grab its globals dictionary
+ ldict = get_caller_module_dict(2)
+
+ # Collect parser information from the dictionary
+ linfo = LexerReflect(ldict,log=errorlog,reflags=reflags)
+ linfo.get_all()
+ if not optimize:
+ if linfo.validate_all():
+ raise SyntaxError("Can't build lexer")
if optimize and lextab:
try:
- _read_lextab(lexer,ldict, lextab)
- if not lexer.lexignore: lexer.lexignore = ""
- token = lexer.token
- input = lexer.input
- return lexer
-
+ lexobj.readtab(lextab,ldict)
+ token = lexobj.token
+ input = lexobj.input
+ lexer = lexobj
+ return lexobj
+
except ImportError:
pass
-
- # Get the tokens map
- tokens = ldict.get("tokens",None)
- if not tokens:
- raise SyntaxError,"lex: module does not define 'tokens'"
- if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)):
- raise SyntaxError,"lex: tokens must be a list or tuple."
+
+ # Dump some basic debugging information
+ if debug:
+ debuglog.info("lex: tokens = %r", linfo.tokens)
+ debuglog.info("lex: literals = %r", linfo.literals)
+ debuglog.info("lex: states = %r", linfo.stateinfo)
# Build a dictionary of valid token names
- lexer.lextokens = { }
- if not optimize:
+ lexobj.lextokens = { }
+ for n in linfo.tokens:
+ lexobj.lextokens[n] = 1
- # Utility function for verifying tokens
- def is_identifier(s):
- for c in s:
- if not (c.isalnum() or c == '_'): return 0
- return 1
-
- for n in tokens:
- if not is_identifier(n):
- print "lex: Bad token name '%s'" % n
- error = 1
- if lexer.lextokens.has_key(n):
- print "lex: Warning. Token '%s' multiply defined." % n
- lexer.lextokens[n] = None
+ # Get literals specification
+ if isinstance(linfo.literals,(list,tuple)):
+ lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals)
else:
- for n in tokens: lexer.lextokens[n] = None
-
+ lexobj.lexliterals = linfo.literals
- if debug:
- print "lex: tokens = '%s'" % lexer.lextokens.keys()
-
- # Get a list of symbols with the t_ prefix
- tsymbols = [f for f in ldict.keys() if f[:2] == 't_']
-
- # Now build up a list of functions and a list of strings
- fsymbols = [ ]
- ssymbols = [ ]
- for f in tsymbols:
- if isinstance(ldict[f],types.FunctionType):
- fsymbols.append(ldict[f])
- elif isinstance(ldict[f],types.StringType):
- ssymbols.append((f,ldict[f]))
- else:
- print "lex: %s not defined as a function or string" % f
- error = 1
-
- # Sort the functions by line number
- fsymbols.sort(lambda x,y: cmp(x.func_code.co_firstlineno,y.func_code.co_firstlineno))
-
- # Sort the strings by regular expression length
- ssymbols.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1])))
-
- # Check for non-empty symbols
- if len(fsymbols) == 0 and len(ssymbols) == 0:
- raise SyntaxError,"lex: no rules of the form t_rulename are defined."
-
- # Add all of the rules defined with actions first
- for f in fsymbols:
-
- line = f.func_code.co_firstlineno
- file = f.func_code.co_filename
- files[file] = None
-
- if not optimize:
- if f.func_code.co_argcount > 1:
- print "%s:%d: Rule '%s' has too many arguments." % (file,line,f.__name__)
- error = 1
- continue
-
- if f.func_code.co_argcount < 1:
- print "%s:%d: Rule '%s' requires an argument." % (file,line,f.__name__)
- error = 1
- continue
+ # Get the stateinfo dictionary
+ stateinfo = linfo.stateinfo
- if f.__name__ == 't_ignore':
- print "%s:%d: Rule '%s' must be defined as a string." % (file,line,f.__name__)
- error = 1
- continue
-
- if f.__name__ == 't_error':
- lexer.lexerrorf = f
- continue
+ regexs = { }
+ # Build the master regular expressions
+ for state in stateinfo:
+ regex_list = []
- if f.__doc__:
- if not optimize:
- try:
- c = re.compile(f.__doc__, re.VERBOSE)
- except re.error,e:
- print "%s:%d: Invalid regular expression for rule '%s'. %s" % (file,line,f.__name__,e)
- error = 1
- continue
+ # Add rules defined by functions first
+ for fname, f in linfo.funcsym[state]:
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ regex_list.append("(?P<%s>%s)" % (fname,_get_regex(f)))
+ if debug:
+ debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,_get_regex(f), state)
- if debug:
- print "lex: Adding rule %s -> '%s'" % (f.__name__,f.__doc__)
+ # Now add all of the simple rules
+ for name,r in linfo.strsym[state]:
+ regex_list.append("(?P<%s>%s)" % (name,r))
+ if debug:
+ debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state)
- # Okay. The regular expression seemed okay. Let's append it to the master regular
- # expression we're building
-
- if (regex): regex += "|"
- regex += "(?P<%s>%s)" % (f.__name__,f.__doc__)
- else:
- print "%s:%d: No regular expression defined for rule '%s'" % (file,line,f.__name__)
+ regexs[state] = regex_list
- # Now add all of the simple rules
- for name,r in ssymbols:
+ # Build the master regular expressions
- if name == 't_ignore':
- lexer.lexignore = r
- continue
-
- if not optimize:
- if name == 't_error':
- raise SyntaxError,"lex: Rule 't_error' must be defined as a function"
- error = 1
- continue
-
- if not lexer.lextokens.has_key(name[2:]):
- print "lex: Rule '%s' defined for an unspecified token %s." % (name,name[2:])
- error = 1
- continue
- try:
- c = re.compile(r,re.VERBOSE)
- except re.error,e:
- print "lex: Invalid regular expression for rule '%s'. %s" % (name,e)
- error = 1
- continue
- if debug:
- print "lex: Adding rule %s -> '%s'" % (name,r)
-
- if regex: regex += "|"
- regex += "(?P<%s>%s)" % (name,r)
+ if debug:
+ debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====")
- if not optimize:
- for f in files.keys():
- if not validate_file(f):
- error = 1
- try:
+ for state in regexs:
+ lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames)
+ lexobj.lexstatere[state] = lexre
+ lexobj.lexstateretext[state] = re_text
+ lexobj.lexstaterenames[state] = re_names
if debug:
- print "lex: regex = '%s'" % regex
- lexer.lexre = re.compile(regex, re.VERBOSE)
+ for i in range(len(re_text)):
+ debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i])
+
+ # For inclusive states, we need to add the regular expressions from the INITIAL state
+ for state,stype in stateinfo.items():
+ if state != "INITIAL" and stype == 'inclusive':
+ lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL'])
+ lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL'])
+ lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL'])
+
+ lexobj.lexstateinfo = stateinfo
+ lexobj.lexre = lexobj.lexstatere["INITIAL"]
+ lexobj.lexretext = lexobj.lexstateretext["INITIAL"]
+ lexobj.lexreflags = reflags
+
+ # Set up ignore variables
+ lexobj.lexstateignore = linfo.ignore
+ lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","")
+
+ # Set up error functions
+ lexobj.lexstateerrorf = linfo.errorf
+ lexobj.lexerrorf = linfo.errorf.get("INITIAL",None)
+ if not lexobj.lexerrorf:
+ errorlog.warning("No t_error rule is defined")
+
+ # Check state information for ignore and error rules
+ for s,stype in stateinfo.items():
+ if stype == 'exclusive':
+ if not s in linfo.errorf:
+ errorlog.warning("No error rule is defined for exclusive state '%s'", s)
+ if not s in linfo.ignore and lexobj.lexignore:
+ errorlog.warning("No ignore rule is defined for exclusive state '%s'", s)
+ elif stype == 'inclusive':
+ if not s in linfo.errorf:
+ linfo.errorf[s] = linfo.errorf.get("INITIAL",None)
+ if not s in linfo.ignore:
+ linfo.ignore[s] = linfo.ignore.get("INITIAL","")
- # Build the index to function map for the matching engine
- lexer.lexindexfunc = [ None ] * (max(lexer.lexre.groupindex.values())+1)
- for f,i in lexer.lexre.groupindex.items():
- handle = ldict[f]
- if isinstance(handle,types.FunctionType):
- lexer.lexindexfunc[i] = (handle,handle.__name__[2:])
- else:
- # If rule was specified as a string, we build an anonymous
- # callback function to carry out the action
- lexer.lexindexfunc[i] = (None,f[2:])
-
- # If a lextab was specified, we create a file containing the precomputed
- # regular expression and index table
-
- if lextab and optimize:
- lt = open(lextab+".py","w")
- lt.write("# %s.py. This file automatically created by PLY. Don't edit.\n" % lextab)
- lt.write("_lexre = %s\n" % repr(regex))
- lt.write("_lextab = [\n");
- for i in range(0,len(lexer.lexindexfunc)):
- t = lexer.lexindexfunc[i]
- if t:
- if t[0]:
- lt.write(" ('%s',%s),\n"% (t[0].__name__, repr(t[1])))
- else:
- lt.write(" (None,%s),\n" % repr(t[1]))
- else:
- lt.write(" None,\n")
-
- lt.write("]\n");
- lt.write("_lextokens = %s\n" % repr(lexer.lextokens))
- lt.write("_lexignore = %s\n" % repr(lexer.lexignore))
- if (lexer.lexerrorf):
- lt.write("_lexerrorf = %s\n" % repr(lexer.lexerrorf.__name__))
- else:
- lt.write("_lexerrorf = None\n")
- lt.close()
-
- except re.error,e:
- print "lex: Fatal error. Unable to compile regular expression rules. %s" % e
- error = 1
- if error:
- raise SyntaxError,"lex: Unable to build lexer."
- if not lexer.lexerrorf:
- print "lex: Warning. no t_error rule is defined."
-
- if not lexer.lexignore: lexer.lexignore = ""
-
# Create global versions of the token() and input() functions
- token = lexer.token
- input = lexer.input
-
- return lexer
+ token = lexobj.token
+ input = lexobj.input
+ lexer = lexobj
+
+ # If in optimize mode, we write the lextab
+ if lextab and optimize:
+ lexobj.writetab(lextab,outputdir)
+
+ return lexobj
# -----------------------------------------------------------------------------
-# run()
+# runmain()
#
# This runs the lexer as a main program
# -----------------------------------------------------------------------------
@@ -658,7 +1030,7 @@ def runmain(lexer=None,data=None):
data = f.read()
f.close()
except IndexError:
- print "Reading from standard input (type EOF to end):"
+ sys.stdout.write("Reading from standard input (type EOF to end):\n")
data = sys.stdin.read()
if lexer:
@@ -670,12 +1042,28 @@ def runmain(lexer=None,data=None):
_token = lexer.token
else:
_token = token
-
+
while 1:
tok = _token()
if not tok: break
- print "(%s,'%s',%d)" % (tok.type, tok.value, tok.lineno)
-
-
+ sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos))
+
+# -----------------------------------------------------------------------------
+# @TOKEN(regex)
+#
+# This decorator function can be used to set the regex expression on a function
+# when its docstring might need to be set in an alternative way
+# -----------------------------------------------------------------------------
+
+def TOKEN(r):
+ def set_regex(f):
+ if hasattr(r,"__call__"):
+ f.regex = _get_regex(r)
+ else:
+ f.regex = r
+ return f
+ return set_regex
+# Alternative spelling of the TOKEN decorator
+Token = TOKEN
diff --git a/patron.rng.expected b/patron.rng.expected
new file mode 120000
index 0000000..f9c028b
--- /dev/null
+++ b/patron.rng.expected
@@ -0,0 +1 @@
+patron.rng.orig \ No newline at end of file
diff --git a/patron.rng b/patron.rng.orig
index 582ad0f..582ad0f 100644
--- a/patron.rng
+++ b/patron.rng.orig
diff --git a/patron.rng.trang b/patron.rng.trang
new file mode 120000
index 0000000..fd81865
--- /dev/null
+++ b/patron.rng.trang
@@ -0,0 +1 @@
+patron.rng.expected \ No newline at end of file
diff --git a/regextest.rng.expected b/regextest.rng.expected
new file mode 100644
index 0000000..62f372d
--- /dev/null
+++ b/regextest.rng.expected
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- A schema for regextest.rnc in RELAX NG compact syntax -->
+<element name="testSuite" xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="testCase">
+ <choice>
+ <group>
+ <element name="correct">
+ <text/>
+ </element>
+ <zeroOrMore>
+ <element>
+ <choice>
+ <name>valid</name>
+ <name>invalid</name>
+ </choice>
+ <text/>
+ </element>
+ </zeroOrMore>
+ </group>
+ <element name="incorrect">
+ <text/>
+ </element>
+ </choice>
+ </element>
+ </zeroOrMore>
+</element>
diff --git a/regextest.rng.trang b/regextest.rng.trang
new file mode 120000
index 0000000..e67489e
--- /dev/null
+++ b/regextest.rng.trang
@@ -0,0 +1 @@
+regextest.rng.expected \ No newline at end of file
diff --git a/res08.rng.expected b/res08.rng.expected
new file mode 100644
index 0000000..5fedeab
--- /dev/null
+++ b/res08.rng.expected
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0">
+ <start>
+ <element name="foo">
+ <ref name="foo.body"/>
+ </element>
+ </start>
+</grammar>
diff --git a/res08.rng b/res08.rng.orig
index 556656a..556656a 100644
--- a/res08.rng
+++ b/res08.rng.orig
diff --git a/res08.rng.trang b/res08.rng.trang
new file mode 120000
index 0000000..918e35a
--- /dev/null
+++ b/res08.rng.trang
@@ -0,0 +1 @@
+res08.rng.expected \ No newline at end of file
diff --git a/rnc_tokenize.py b/rnc_tokenize.py
index 20cfc34..554e5ec 100644
--- a/rnc_tokenize.py
+++ b/rnc_tokenize.py
@@ -1,108 +1,265 @@
#!/usr/bin/env python
-
# Define the tokenizer for RELAX NG compact syntax
# This file released to the Public Domain by David Mertz
+#
+# Extended under revised BSD license by Jan Pokorny (jpokorny@redhat.com)
+# Copyright 2013 Red Hat, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+# TODO: update positions so they can be used for debugging
+
+import re
+#try:
+# from ply import lex
+#except ImportError:
+# import lex
import lex
-tokens = tuple('''
- ELEM ATTR EMPTY TEXT KEYWORD LITERAL ANNOTATION COMMENT
- BEG_PAREN END_PAREN BEG_BODY END_BODY EQUAL NAME CHOICE SEQ
- INTERLEAVE ANY SOME MAYBE WHITESPACE TODO DATATAG PATTERN
- DEFAULT_NS NS DATATYPES NS_ANNOTATION START DEFINE
+
+
+#
+# tokens declaration
+#
+
+# square (left + right delimiter) together with single-node tree replacement
+pair_rules = tuple(l.replace(' ', '').split(':') for l in '''
+ BEG_BODY : END_BODY : BODY
+ BEG_PAREN : END_PAREN : GROUP
+ BEG_ANNO : END_ANNO : NS_ANNOTATION
+ '''.strip().splitlines())
+pair_tokens = reduce(lambda a, b: a + tuple(b[:2]), pair_rules, ())
+
+quant_tokens = tuple('''
+ ANY
+ MAYBE
+ SOME
'''.split())
-reserved = {
- 'element' : 'ELEM',
- 'attribute' : 'ATTR',
- 'empty' : 'EMPTY',
- 'text' : 'TEXT',
- 'div' : 'TODO',
- 'external' : 'TODO',
- 'grammar' : 'TODO',
- 'include' : 'TODO',
- 'inherit' : 'TODO',
- 'list' : 'TODO',
- 'mixed' : 'TODO',
- 'notAllowed' : 'TODO',
- 'parent' : 'TODO',
- 'string' : 'TODO',
- 'token' : 'TODO',
+immediate_tokens = tuple('''
+ ANNOTATION
+ CHOICE
+ COMMENT
+ DATATAG
+ DEFINE
+ EQUAL
+ INTERLEAVE
+ LITERAL
+ NAME
+ PATTERN
+ SEQ
+ WHITESPACE
+ '''.split()) + quant_tokens
+
+# http://relaxng.org/compact-20021121.html#syntax
+keywords = {
+ 'attribute': 'ATTR',
+ 'default': 'DEFAULT_NS',
+ 'datatypes': 'DATATYPES',
+ 'div': 'DIV',
+ 'element': 'ELEM',
+ 'empty': 'EMPTY',
+ 'external': 'EXTERNAL',
+ 'grammar': 'GRAMMAR',
+ 'include': 'INCLUDE',
+ 'inherit': 'INHERIT',
+ 'list': 'LIST',
+ 'mixed': 'MIXED',
+ 'namespace': 'NS',
+ 'notAllowed': 'NOTALLOWED',
+ 'parent': 'PARENT',
+ 'start': 'START',
+ 'string': 'STRING',
+ 'text': 'TEXT',
+ 'token': 'TOKEN',
}
-def t_START(t):
- r"(?im)^start\s*=\s*.*$"
- t.value = t.value.split('=')[1].strip()
- return t
+tokens = immediate_tokens + pair_tokens + tuple(keywords.values())
+
+
+#
+# tokens definition
+#
+
+# RELAX NG/XML datatype NCName is defined in
+# http://books.xmlschemata.org/relaxng/ch19-77215.html
+# http://www.w3.org/TR/REC-xml-names/#NT-NCName
+# which can be resolved as
+# <NCName> ::= <Start> <NonStart>*
+# where
+# <Start> ::= [A-Z] | "_" | [a-z] | [#xC0-#xD6] | ...
+# (see http://www.w3.org/TR/REC-xml/#NT-NameStartChar)
+# <NonStart> ::= <Start> | "-" | "." | [0-9] | #xB7 | ..
+# (see http://www.w3.org/TR/REC-xml/#NT-NameChar)
+#
+# NOTE: skipping [\u10000-\uEFFFF] as it won't get lex'd (???)
+NCName_start = "(?:[A-Z_a-z]" \
+ u"|[\u00C0-\u00D6]" \
+ u"|[\u00D8-\u00F6]" \
+ u"|[\u00F8-\u02FF]" \
+ u"|[\u0370-\u037D]" \
+ u"|[\u037F-\u1FFF]" \
+ u"|[\u200C-\u200D]" \
+ u"|[\u2070-\u218F]" \
+ u"|[\u2C00-\u2FEF]" \
+ u"|[\u3001-\uD7FF]" \
+ u"|[\uF900-\uFDCF]" \
+ u"|[\uFDF0-\uFFFD]" \
+ ")"
+NCName_nonstart = NCName_start + "|(?:[-.0-9]" \
+ u"|\u00B7" \
+ u"|[\u0300-\u036F]" \
+ u"|[\u203F-\u2040]" \
+ ")"
+NCName = NCName_start + "(?:" + NCName_nonstart + ")*"
+
+# lex internals
+
+t_ignore = " \t\n"
+
+
+def t_error(t):
+ try:
+ t.lexer.skip(1)
+ except AttributeError:
+ # works in historic version of PLY
+ t.skip(1)
+
+# immediate tokens
+
+t_ANY = r'\*'
+t_CHOICE = r'\|'
+t_EQUAL = r'='
+t_INTERLEAVE = r'&'
+t_MAYBE = r'\?'
+t_SEQ = r','
+t_SOME = r'\+'
+t_WHITESPACE = r'\s+'
-def t_DEFINE(t):
- r"(?im)^[\w-]+\s*="
- t.value = t.value.split('=')[0].strip()
- return t
def t_ANNOTATION(t):
- r"(?im)^\#\# .*$"
- t.value = t.value[3:]
+ r"\#\#[ \t]?.*"
+ t.value = t.value.replace('# ', '#', 1).split('##', 1)[1].rstrip()
return t
+
def t_COMMENT(t):
- r"(?im)^\# .*$"
- t.value = t.value[2:]
+ r"\#[ \t]?.*"
+ t.value = t.value.replace('# ', '#', 1).split('#', 1)[1].rstrip()
return t
-def t_DEFAULT_NS(t):
- r"(?im)default\s+namespace\s*=\s*.*$"
- t.value = t.value.split('=')[1].strip()
- return t
def t_DATATYPES(t):
- r"(?im)datatypes\s+xsd\s*=\s*.*$"
- t.value = t.value.split('=')[1].strip()
+ r"datatypes\s+xsd\s*=\s*.*"
+ t.value = t.value.split('=', 1)[1].strip()
return t
+
def t_DATATAG(t):
r"xsd:\w+"
- t.value = t.value.split(':')[1]
+ t.value = t.value.split(':', 1)[1]
+ return t
+
+
+def t_DEFAULT_NS(t):
+ r"default\s+namespace\s*=\s*.*"
+ t.value = t.value.split('=', 1)[1].strip()
+ return t
+
+
+def t_INCLUDE(t):
+ t.value = t.value.split('"', 1)[1][:-1]
+ return t
+t_INCLUDE.__doc__ = r'include\s*"' + NCName + '"'
+
+
+def t_LITERAL(t):
+ r'".+?"(?:\s*[~]\s*".+?")*'
+ t.value = ' '.join(i.strip(' \n"') for i in t.value.split('~'))
return t
+
+def t_NAME(t):
+ # "In order to use a keyword as an identifier, it must be quoted with \."
+ t.value = t.value[2:]
+ return t
+t_NAME.__doc__ = r"\\[.]" + NCName
+
+
+def t_NS(t):
+ r"namespace\s+.*"
+ t.value = t.value.split(None, 1)[1]
+ return t
+
+
def t_PATTERN(t):
r'{\s*pattern\s*=\s*".*"\s*}'
- t.value = t.value[:-1].split('=')[1].strip()[1:-1]
+ t.value = t.value[:-1].split('=', 1)[1].strip()[1:-1]
return t
-def t_NS(t):
- r"(?im)^namespace\s+.*$"
- t.value = t.value.split(None,1)[1]
+
+# these two last (and in this order) for reason
+def t_DEFINE(t):
+ t.value = t.value.split('=', 1)[0].strip()
return t
+t_DEFINE.__doc__ = NCName + "\s*="
+
def t_ID(t):
- r"[\w:_-]+"
- t.type = reserved.get(t.value,'NAME') # Check for reserved words
+ t.type = keywords.get(t.value, 'NAME') # Check for keywords
return t
+t_ID.__doc__ = NCName
-def t_LITERAL(t):
- r'".+?"'
- t.value = t.value[1:-1]
- return t
-t_BEG_PAREN = r"\("
-t_END_PAREN = r"\)"
-t_BEG_BODY = r"{"
-t_END_BODY = r"}"
-t_EQUAL = r"="
-t_CHOICE = r"[|]"
-t_SEQ = r","
-t_INTERLEAVE= r"&"
-t_ANY = r"[*]"
-t_SOME = r"[+]"
-t_MAYBE = r"[?]"
-t_WHITESPACE= r"\s+"
-t_ignore = " \t\n\r"
+# pair tokens
+
+t_BEG_ANNO = r'\['
+t_END_ANNO = r'\]'
+t_BEG_BODY = r'{'
+t_END_BODY = r'}'
+t_BEG_PAREN = r'\('
+t_END_PAREN = r'\)'
+
+
+#
+# processing
+#
+
+def preprocess(rnc):
+ # 2.2. BOM stripping
+ if len(rnc) >= 2 and ord(rnc[0]) == 0xFE and ord(rnc[1]) == 0xFF:
+ rnc = rnc[2:]
+ # 2.3 Newline normalization
+ rnc = re.sub(r"(?:\n|\r\n?)", "\n", rnc, re.MULTILINE)
+ # TODO: 2.4 Escape interpretation
+ return rnc
-def t_error(t):
- t.skip(1)
def token_list(rnc):
lex.lex()
- lex.input(rnc)
+ lex.input(preprocess(rnc))
ts = []
while 1:
t = lex.token()
@@ -111,7 +268,8 @@ def token_list(rnc):
ts.append(t)
return ts
-if __name__=='__main__':
+
+if __name__ == '__main__':
import sys
del t_ignore
tokens = token_list(sys.stdin.read())
diff --git a/rnctree.py b/rnctree.py
index ebb9433..eb2d714 100755
--- a/rnctree.py
+++ b/rnctree.py
@@ -1,49 +1,129 @@
#!/usr/bin/env python
# Convert an RELAX NG compact syntax schema to a Node tree
# This file released to the Public Domain by David Mertz
-from __future__ import generators
+#
+# Extended under revised BSD license by Jan Pokorny (jpokorny@redhat.com)
+# Copyright 2013 Red Hat, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+# Differences when compared to trang output
+# 1. comments placement
+# 2. sometimes superfluous <group>
+# 3. context-free dichotomy (diff conv08.rng.{expected,trang})
+# plenty of others (it's not the primary goal to achieve 1:1 trang match)
+
+# XXX: each AST node has its own subclass, knows how to XMLize itself, ...?
+
+
import sys
-from rnc_tokenize import token_list
+from rnc_tokenize import tokens, pair_rules, keywords, token_list
+
+# ONE ... default cardinality of one
+# DIRECT ... denotes that the usage of NAME is <name>, not <ref name=...>
+quant_tokens_aux = tuple('''
+ DIRECT
+ ONE
+ '''.split())
+# AST nodes not directly matching the tokens
+parse_constructs = tuple('''
+ ROOT
+ '''.split()) + tuple(r[2] for r in pair_rules)
-class ParseError(SyntaxError): pass
+for t in tokens + quant_tokens_aux + parse_constructs:
+ globals()[t] = t
-for t in """
- ANY SOME MAYBE ONE BODY ANNOTATION ELEM ATTR GROUP LITERAL
- NAME COMMENT TEXT EMPTY INTERLEAVE CHOICE SEQ ROOT
- DEFAULT_NS NS DATATYPES DATATAG PATTERN START DEFINE
- """.split(): globals()[t] = t
+keyword_list = keywords.values()
-PAIRS = {'BEG_BODY': ('END_BODY', BODY),
- 'BEG_PAREN': ('END_PAREN', GROUP),
- 'BEG_ANNO': ('END_ANNO', ANNOTATION)}
+PAIRS = {r[0]: tuple(r[1:]) for r in pair_rules}
-TAGS = {ONE: 'group',
- SOME: 'oneOrMore',
- MAYBE: 'optional',
- ANY: 'zeroOrMore'}
+TAGS = {
+ ONE: 'group',
+ SOME: 'oneOrMore',
+ MAYBE: 'optional',
+ ANY: 'zeroOrMore',
+ ELEM: 'element',
+ ATTR: 'attribute',
+ NAME: 'ref',
+}
+
+URI_DATATYPES = "http://www.w3.org/2001/XMLSchema-datatypes"
+URI_ANNOTATIONS = "http://relaxng.org/ns/compatibility/annotations/1.0"
DEFAULT_NAMESPACE = None
-DATATYPE_LIB = [0, '"http://www.w3.org/2001/XMLSchema-datatypes"']
+DATATYPE_LIB = [0, '"' + URI_DATATYPES + '"']
OTHER_NAMESPACE = {}
CONTEXT_FREE = 0
-try: enumerate
-except: enumerate = lambda seq: zip(range(len(seq)), seq)
+# debugging
+for i, n in enumerate("""
+ D_NOTHING
+ D_TO_NODES
+ D_MATCH_PAIR
+ D_TYPE_BODIES
+ D_NEST_DEFINES
+ D_SCAN_NS
+""".split()):
+ globals()[n] = i and 2 << (i - 1) or 0
+dlist = []
+#dlist.append(D_TO_NODES)
+#dlist.append(D_MATCH_PAIR)
+#dlist.append(D_TYPE_BODIES)
+#dlist.append(D_NEST_DEFINES)
+#dlist.append(D_SCAN_NS)
+debug = reduce(lambda a, b: a | b, dlist, D_NOTHING)
+
+
+def try_debug(what, nodes):
+ if debug & globals().get('D_' + what, D_NOTHING):
+ print what
+ for node in nodes:
+ print node.prettyprint()
+
+
nodetypes = lambda nl: tuple(map(lambda n: n.type, nl))
toNodes = lambda toks: map(lambda t: Node(t.type, t.value), toks)
+class ParseError(SyntaxError):
+ pass
+
+
class Node(object):
__slots__ = ('type', 'value', 'name', 'quant')
- def __iter__(self): yield self
+ def __iter__(self):
+ yield self
__len__ = lambda self: 1
- def __init__(self, type='', value=[], name=None, quant=ONE):
- self.type = type
- self.value = value
- self.name = name
+ def __init__(self, type='', value=None, name=None, quant=ONE):
+ self.type = type
+ self.value = value if value is not None else []
+ self.name = name
self.quant = quant
def format(self, indent=0):
@@ -73,26 +153,58 @@ class Node(object):
else:
return self.add_ns(self.xmlnode())
+ def collect_annot(self, x):
+ ret = {}
+ if isinstance(x.value, basestring):
+ return ret
+
+ name, value = None, None
+ for node in x.value:
+ if node.type != NS_ANNOTATION:
+ break
+ for i, inner in enumerate(node.value):
+ if i % 3 == 0 and inner.type == NAME:
+ name = inner.value
+ elif i % 3 == 1 and inner.type == DEFINE:
+ name += ':' + inner.value
+ elif i % 3 == 2 and inner.type == LITERAL:
+ value = inner.value
+ if ret.setdefault(name, value) is not value:
+ assert 0, "redefinition of %s" % name
+ name, value = None, None
+ elif i % 3 == 0 and i > 0:
+ break
+ else:
+ assert 0, "NS_ANNOTATION body does not match"
+ return [n + '="' + v + '"' for n, v in ret.iteritems()]
+
def xmlnode(self, indent=0):
out = []
write = out.append
if self.type == ROOT:
write('<?xml version="1.0" encoding="UTF-8"?>')
- for x in self.value:
+ for i, x in enumerate(self.value):
if not isinstance(x, Node):
raise TypeError("Unhappy Node.value: " + repr(x))
- elif x.type == START:
- startelem = '<start><ref name="%s"/></start>' % x.value
- write(' ' * indent + startelem)
+ if x.type == START:
+ write(' ' * indent + '<start>')
+ if (x.name is not None):
+ write(' ' * (indent + 1) + '<ref name="%s"/>' % x.name)
+ else:
+ write(x.xmlnode(indent + 1))
+ write(' ' * indent + '</start>')
elif x.type == DEFINE:
write(' ' * indent + '<define name="%s">' % x.name)
write(x.xmlnode(indent + 1))
write(' ' * indent + '</define>')
- elif x.type == NAME:
- write(' ' * indent + '<ref name="%s"/>' % x.value)
elif x.type == COMMENT:
- write(' ' * indent + '<!-- %s -->' % x.value)
+ comments = x.value.split('\n')
+ if len(comments) == 1:
+ c = ' ' + comments[0] + ' '
+ else:
+ c = ('\n' + ' ' * (indent + 1)).join([''] + comments + [''])
+ write(' ' * indent + '<!--%s-->' % c)
elif x.type == LITERAL:
write(' ' * indent + '<value>%s</value>' % x.value)
elif x.type == ANNOTATION:
@@ -102,14 +214,14 @@ class Node(object):
write(' ' * indent + '<interleave>')
write(x.xmlnode(indent + 1))
write(' ' * indent + '</interleave>')
- elif x.type == SEQ:
- write(x.xmlnode(indent + 1))
elif x.type == CHOICE:
write(' ' * indent + '<choice>')
write(x.xmlnode(indent + 1))
write(' ' * indent + '</choice>')
- elif x.type == GROUP:
- write(x.xmlnode(indent))
+ elif x.type in (GROUP, SEQ):
+ write(' ' * indent + '<group>')
+ write(x.xmlnode(indent + 1))
+ write(' ' * indent + '</group>')
elif x.type == TEXT:
write(' ' * indent + '<text/>')
elif x.type == EMPTY:
@@ -123,24 +235,32 @@ class Node(object):
p = '<param name="pattern">%s</param>' % x.value
write(' ' * (indent + 1) + p)
write(' ' * indent + '</data>')
- elif x.type == ELEM:
- if x.quant == ONE:
- write(' ' * indent + '<element name="%s">' % x.name)
- write(x.xmlnode(indent + 1))
- write(' ' * indent + '</element>')
+ elif x.type == INCLUDE:
+ write(' ' * indent + '<include href="%s"/>' % x.value)
+ elif x.type == NAME and x.quant == DIRECT:
+ assert x.type == NAME
+ write(' ' * indent + '<name>%s</name>' % x.value)
+ elif x.type in (ATTR, ELEM, NAME):
+ a = ('\n' + ' ' * (indent + 3)).join(self.collect_annot(x))
+ name_n_annot = '%s' % (' ' + a).rstrip()
+ name = x.value if x.type == NAME else x.name
+ if name:
+ name_n_annot = ' name="%s"' % name + name_n_annot
+
+ indent_inner = indent
+ if x.quant != ONE:
+ write(' ' * indent_inner + '<%s>' % TAGS[x.quant])
+ indent_inner += 1
+ tag, rest = TAGS[x.type], name_n_annot
+ if x.type == NAME or x.type == ATTR and x.value[0].type == TEXT:
+ write(' ' * indent_inner + '<%s%s/>' % (tag, rest))
else:
- write(' ' * indent + '<%s>' % TAGS[x.quant])
- write(' ' * (indent + 1) + '<element name="%s">' % x.name)
- write(x.xmlnode(indent + 2))
- write(' ' * (indent + 1) + '</element>')
- write(' ' * indent + '</%s>' % TAGS[x.quant])
- elif x.type == ATTR:
- if x.value[0].type == TEXT:
- write(' ' * indent + '<attribute name="%s"/>' % x.name)
- elif x.value[0].type == EMPTY:
- write(' ' * indent + '<attribute name="%s">' % x.name)
- write(' ' * (indent + 1) + '<empty/>')
- write(' ' * indent + '</attribute>')
+ write(' ' * indent_inner + '<%s%s>' % (tag, rest))
+ write(x.xmlnode(indent_inner + 1))
+ write(' ' * indent_inner + '</%s>' % tag)
+ if x.quant != ONE:
+ indent_inner -= 1
+ write(' ' * indent_inner + '</%s>' % TAGS[x.quant])
return '\n'.join(out)
@@ -156,7 +276,11 @@ class Node(object):
ltpos = line.find('<')
if ltpos >= 0 and line[ltpos + 1] not in ('!', '?'):
# We've got an element tag, not PI or comment
- new = line[:line.find('>')]
+ tail = '>'
+ new = line[:line.find(tail)]
+ if new.endswith('/'):
+ new = new[:-1]
+ tail = '/' + tail
new += ' xmlns="http://relaxng.org/ns/structure/1.0"'
if DEFAULT_NAMESPACE is not None:
new += '\n ns=%s' % DEFAULT_NAMESPACE
@@ -164,7 +288,7 @@ class Node(object):
new += '\n datatypeLibrary=%s' % DATATYPE_LIB[1]
for ns, url in OTHER_NAMESPACE.items():
new += '\n xmlns:%s=%s' % (ns, url)
- new += '>'
+ new += tail
lines[i] = new
break
return '\n'.join(lines)
@@ -188,21 +312,38 @@ def findmatch(beg, nodes, offset):
level = 1
end = PAIRS[beg][0]
for i, t in enumerate(nodes[offset:]):
- if t.type == beg: level += 1
- elif t.type == end: level -= 1
+ if t.type == beg:
+ level += 1
+ elif t.type == end:
+ level -= 1
if level == 0:
return i + offset
raise EOFError("No closing token encountered for %s @ %d"
- % (beg, offset))
+ % (beg, offset))
+
+#
+# 1st pass in the pipe
+#
def match_pairs(nodes):
+ """<left paren., []> + <tokens> + <right paren., []> --> <ent., <tokens>>
+
+ Other effects:
+ - merge comments/annotations
+ """
newnodes = []
i = 0
while 1:
- if i >= len(nodes): break
+ if i >= len(nodes):
+ break
node = nodes[i]
if node.type in PAIRS.keys():
+ # TOKEN, etc. -> NAME where suitable
+ # (keyword-like names do not need to be escaped in some cases)
+ if node.type == 'BEG_BODY' and newnodes[-1].type in keyword_list:
+ if newnodes[-2].type in (ELEM, ATTR):
+ newnodes[-1].type = NAME
# Look for enclosing brackets
match = findmatch(node.type, nodes, i + 1)
matchtype = PAIRS[node.type][1]
@@ -210,69 +351,139 @@ def match_pairs(nodes):
node.value = match_pairs(node.value)
newnodes.append(node)
i = match + 1
+ elif (node.type in (COMMENT, ANNOTATION) and i > 0
+ and newnodes[-1].type == node.type):
+ # merge comments/annotations
+ newnodes[-1].value += "\n" + node.value
+ i += 1
else:
newnodes.append(node)
i += 1
- if i >= len(nodes): break
+ if i >= len(nodes):
+ break
if nodes[i].type in (ANY, SOME, MAYBE):
newnodes[-1].quant = nodes[i].type
i += 1
+
nodes[:] = newnodes
return nodes
+#
+# 2nd pass in the pipe
+#
+
def type_bodies(nodes):
+ """Another (main) de-linearization"""
newnodes = []
i = 0
while 1:
- if i >= len(nodes): break
- if nodetypes(nodes[i:i + 3]) == (ELEM, NAME, BODY) or \
- nodetypes(nodes[i:i + 3]) == (ATTR, NAME, BODY):
+ if i >= len(nodes):
+ break
+ if (nodetypes(nodes[i:i + 3]) == (ELEM, NAME, BODY)
+ or nodetypes(nodes[i:i + 3]) == (ATTR, NAME, BODY)):
name, body = nodes[i + 1].value, nodes[i + 2]
value, quant = type_bodies(body.value), body.quant
node = Node(nodes[i].type, value, name, quant)
newnodes.append(node)
+ if not name:
+ assert False
i += 3
+ # "element a|b" cases
+ elif (nodetypes(nodes[i:i + 3]) == (ELEM, NAME, CHOICE)
+ or nodetypes(nodes[i:i + 3]) == (ATTR, NAME, CHOICE)):
+ # see nameClass (choice of nameClass+)
+ # XXX: very simplified
+ if nodes[i].type == ATTR:
+ assert False
+ node_type = nodes[i].type
+ value = [nodes[i + 1]]
+ i += 2
+ while nodetypes(nodes[i:i + 2]) == (CHOICE, NAME):
+ value.extend(type_bodies(nodes[i:i + 2]))
+ i += 2
+ # re-mark quant as we do not want "ref" output here
+ for v in value:
+ if v.type == NAME:
+ v.quant = DIRECT
+ assert len(nodes) >= i and nodes[i].type == BODY
+ value.extend(type_bodies(nodes[i].value))
+ node = Node(node_type, value, None, nodes[i].quant)
+ i += 1
+ newnodes.append(node)
elif nodetypes(nodes[i:i + 2]) == (DATATAG, PATTERN):
node = Node(DATATAG, nodes[i + 1].value, nodes[i].value)
newnodes.append(node)
i += 2
- elif nodes[i] == DEFINE:
- print nodes[i:]
else:
- if nodes[i].type == GROUP: # Recurse into groups
- value = type_bodies(nodes[i].value)
- nodes[i] = Node(GROUP, value, None, nodes[i].quant)
- newnodes.append(nodes[i])
+ n = nodes[i]
+ if n.type == GROUP: # Recurse into groups
+ value = type_bodies(n.value)
+ if len(value) > 1 and n.type:
+ n = Node(GROUP, value, None, n.quant)
+ newnodes.append(n)
i += 1
nodes[:] = newnodes
return nodes
-def nest_defines(nodes):
- "Attach groups to named patterns"
+#
+# 3rd pass in the pipe
+#
+
+def _nest_annotations(nodes, mapping, delim=None):
+ """Helper to move comments/annotations down into attributes/elements
+
+ Uses non-tail recursion to proceed the tree bottom-up as
+ otherwise there would be confusion if the annotations are
+ newly added (and thus should be kept) or the original ones
+ to be moved.
+
+ Mapping is partially defined
+ token-type |-> accumulator-list for token-type
+ for token-types covering annotations (ANNOTATION, NS_ANNOTATION)
+ and is used to pass unconsumed annotations down the tree.
+
+ Returns triplet: number of consumed nodes, filtered nodes, mapping.
+
+ Note that mapping should contain empty lists only when the recursion
+ returns back to the initiator (XXX: little bit of sanity checking,
+ we cannot speak about proper validation here).
+ """
+ # XXX: unclean, yes
newnodes = []
- i = 0
- while 1:
- if i >= len(nodes): break
- node = nodes[i]
- newnodes.append(node)
- if node.type == DEFINE:
- group = []
- while (i + 1) < len(nodes) and nodes[i + 1].type != DEFINE:
- group.append(nodes[i + 1])
- i += 1
- node.name = node.value
- node.value = Node(GROUP, group)
- i += 1
+ for i, n in enumerate(nodes):
+ if delim and n.type == delim:
+ break
+
+ if not isinstance(n.value, str): # no recurse to terminal str
+ if n.type in (ELEM, ATTR):
+ mapping_rec = {n: [] for n in
+ (ANNOTATION, NS_ANNOTATION, COMMENT)}
+ else:
+ mapping_rec = mapping
+ _nest_annotations(n.value, mapping_rec)
+
+ if n.type in (ELEM, ATTR): # annot. consumer (guarded in recursion)
+ n.value = (mapping['NS_ANNOTATION'] + mapping['ANNOTATION']
+ + mapping['COMMENT'] + n.value)
+ mapping['NS_ANNOTATION'][:], mapping['ANNOTATION'][:] = [], []
+ mapping['COMMENT'][:] = []
+ elif i == len(nodes) - 1 and n.type == COMMENT and not delim:
+ # comment at the end of the nodelist, but only if not top-level
+ newnodes.append(n)
+ continue
+
+ mapping.get(n.type, newnodes).append(n)
+
nodes[:] = newnodes
- return nodes
+ return i, nodes, mapping
-def intersperse(nodes):
- "Look for interleaved, choice, or sequential nodes in groups/bodies"
+def _intersperse(nodes):
+ """Look for interleaved, choice, or sequential nodes in groups/bodies"""
for node in nodes:
- if node.type in (ELEM, ATTR, GROUP, LITERAL):
+ if node.type in (ELEM, ATTR, GROUP, LITERAL): # XXX: literal?
val = node.value
ntypes = [n.type for n in val if not isinstance(val, str)]
inters = [t for t in ntypes if t in (INTERLEAVE, CHOICE, SEQ)]
@@ -281,43 +492,117 @@ def intersperse(nodes):
raise ParseError("Ambiguity in sequencing: %s" % node)
if len(inters) > 0:
intertype = inters.keys()[0]
- items = []
+ outer_items, last_ntype, internode = [], None, None
+ simplify = node.type == GROUP
for pat in node.value:
- if pat.type != intertype:
- items.append(pat)
- node.value = Node(intertype, items)
+ if pat.type == intertype:
+ if internode is None:
+ internode = Node(intertype, [outer_items.pop()])
+ outer_items.append(internode)
+ # otherwise drop it
+ elif last_ntype == intertype:
+ internode.value.append(pat)
+ else:
+ outer_items.append(pat)
+ if pat.type in (COMMENT, ANNOTATION):
+ # these are not interesting wrt. last type
+ continue
+ elif pat.quant not in (ONE, MAYBE):
+ simplify = False
+ last_ntype = pat.type
+
+ if (simplify and len(outer_items) == 1
+ and outer_items[0] is internode):
+ node.type, node.value = internode.type, internode.value
+ else:
+ node.value = outer_items
if not isinstance(node.value, str): # No recurse to terminal str
- intersperse(node.value)
+ _intersperse(node.value)
+ return nodes
+
+
+def nest_defines(nodes):
+ """Attach groups to named patterns
+
+ Other effects:
+ - annotations are properly nested
+ - comments are nested
+ """
+ newnodes = []
+ i = 0
+ group, annotations, ns_annotations, comments = [], [], [], []
+ mapping = dict(ANNOTATION=annotations, NS_ANNOTATION=ns_annotations,
+ COMMENT=comments)
+ while i < len(nodes):
+ node = nodes[i]
+ newnodes.append(node)
+ group[:], annotations[:], ns_annotations[:], comments[:] = [], [], [], []
+ if node.type == DEFINE:
+ j, group[:], mapping = _nest_annotations(nodes[i + 1:], mapping, DEFINE)
+ i += j
+ node.name = node.value
+ grp = _intersperse([Node(GROUP, group[:])])[0]
+ if len(grp.value) > 1 and grp.type != SEQ:
+ node.value = [grp]
+ else:
+ node.value = grp.value[:]
+ # when _nest_annotations returned *not* due to reaching DEFINE,
+ # but trailing comments are tolerated
+ if i + 1 > len(nodes) or nodes[i + 1].type not in (DEFINE, COMMENT):
+ break
+ elif node.type == ELEM:
+ # top-level element
+ _intersperse(Node(GROUP, [node]))
+ i += 1
+ nodes[:] = newnodes
return nodes
+#
+# 4th pass in the pipe
+#
+
def scan_NS(nodes):
- "Look for any namespace configuration lines"
+ """Look for any namespace configuration lines
+
+ Other effects:
+ - DEFINE(start) --> START
+ """
global DEFAULT_NAMESPACE, OTHER_NAMESPACE, CONTEXT_FREE
for node in nodes:
if node.type == DEFAULT_NS:
DEFAULT_NAMESPACE = node.value
elif node.type == NS:
- ns, url = map(str.strip, node.value.split('='))
+ ns, url = map(str.strip, node.value.split('=', 1))
OTHER_NAMESPACE[ns] = url
elif node.type == ANNOTATION and 'a' not in OTHER_NAMESPACE:
- OTHER_NAMESPACE['a'] =\
- '"http://relaxng.org/ns/compatibility/annotations/1.0"'
+ OTHER_NAMESPACE['a'] = '"' + URI_ANNOTATIONS + '"'
elif node.type == DATATYPES:
DATATYPE_LIB[:] = [1, node.value]
- elif node.type == START:
+ elif not CONTEXT_FREE and node.type == DEFINE and node.name == 'start':
CONTEXT_FREE = 1
+ node.type = START
+ node.name = None
def make_nodetree(tokens):
+ """Wraps the pipe of conversion passes"""
nodes = toNodes(tokens)
+ try_debug('TO_NODES', nodes)
+
match_pairs(nodes)
+ try_debug('MATCH_PAIR', nodes)
+
type_bodies(nodes)
+ try_debug('TYPE_BODIES', nodes)
+
nest_defines(nodes)
- intersperse(nodes)
+ try_debug('NEST_DEFINES', nodes)
+
scan_NS(nodes)
- root = Node(ROOT, nodes)
- return root
+ try_debug('SCAN_NS', nodes)
+
+ return Node(ROOT, nodes)
if __name__ == '__main__':
diff --git a/tests.sh b/tests.sh
new file mode 100755
index 0000000..4bb66bb
--- /dev/null
+++ b/tests.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Copyright 2013 Red Hat, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+# TODO: the output will never be 1:1 to that from trang, so a subset of
+# test suite has to be run against manually checked outputs
+set -u
+
+SCRIPT=./rnc2rng
+DIFF="diff -q"
+KNOWNTOFAIL="xsdtest.rng" # colon-separated
+KNOWNTOFAIL=":${KNOWNTOFAIL}:"
+
+run() {
+ for rnc in *.rnc; do
+ local rng="$(echo "${rnc}" | sed 's|rnc$|rng|')"
+ echo ":${KNOWNTOFAIL}:" | grep -Fq "${rng}" && continue
+ ${SCRIPT} "${rnc}" > "${rng}.testrun"
+ ${DIFF} ${rng}{.expected,.testrun}
+ [ $? -eq 0 ] && echo "${rnc} OK" || echo "${rnc} FAIL"
+ done
+}
+
+clean() {
+ rm -vf "*.testrun"
+}
+
+[ $# -eq 0 ] && run || $1
diff --git a/conv09.rng b/unused/conv09.rng
index 27628ec..27628ec 100644
--- a/conv09.rng
+++ b/unused/conv09.rng
diff --git a/curious.dtd b/unused/curious.dtd
index 03b6a63..03b6a63 100644
--- a/curious.dtd
+++ b/unused/curious.dtd
diff --git a/curious.xml b/unused/curious.xml
index 6d95294..6d95294 100644
--- a/curious.xml
+++ b/unused/curious.xml
diff --git a/patron-2.rng b/unused/patron-2.rng
index 324138e..324138e 100644
--- a/patron-2.rng
+++ b/unused/patron-2.rng
diff --git a/patron-i1.xml b/unused/patron-i1.xml
index 976be53..976be53 100644
--- a/patron-i1.xml
+++ b/unused/patron-i1.xml
diff --git a/patron-i2.xml b/unused/patron-i2.xml
index f0b38c5..f0b38c5 100644
--- a/patron-i2.xml
+++ b/unused/patron-i2.xml
diff --git a/patron-i3.xml b/unused/patron-i3.xml
index fba69c8..fba69c8 100644
--- a/patron-i3.xml
+++ b/unused/patron-i3.xml
diff --git a/patron-v1.xml b/unused/patron-v1.xml
index 1676598..1676598 100644
--- a/patron-v1.xml
+++ b/unused/patron-v1.xml
diff --git a/patron-v2.xml b/unused/patron-v2.xml
index 06b37bb..06b37bb 100644
--- a/patron-v2.xml
+++ b/unused/patron-v2.xml
diff --git a/res09.rng b/unused/res09.rng
index 46efdaf..46efdaf 100644
--- a/res09.rng
+++ b/unused/res09.rng
diff --git a/spectest.xml b/unused/spectest.xml
index b43d460..b43d460 100644
--- a/spectest.xml
+++ b/unused/spectest.xml
diff --git a/split.xsl b/unused/split.xsl
index bef75f0..bef75f0 100644
--- a/split.xsl
+++ b/unused/split.xsl
diff --git a/testSuite.rng b/unused/testSuite.rng
index 6626f3a..6626f3a 100644
--- a/testSuite.rng
+++ b/unused/testSuite.rng
diff --git a/patron.xsd b/unused/unused-counterparts/patron.xsd
index 420bb3d..420bb3d 100644
--- a/patron.xsd
+++ b/unused/unused-counterparts/patron.xsd
diff --git a/regextest.xml b/unused/unused-counterparts/regextest.xml
index b6f896d..b6f896d 100644
--- a/regextest.xml
+++ b/unused/unused-counterparts/regextest.xml
diff --git a/xsdtest.xml b/unused/unused-counterparts/xsdtest.xml
index 2fe94b6..2fe94b6 100644
--- a/xsdtest.xml
+++ b/unused/unused-counterparts/xsdtest.xml
diff --git a/xsdtest.xsl b/unused/unused-counterparts/xsdtest.xsl
index 1290b16..1290b16 100644
--- a/xsdtest.xsl
+++ b/unused/unused-counterparts/xsdtest.xsl
diff --git a/xsdtest.rng.expected b/xsdtest.rng.expected
new file mode 120000
index 0000000..a988708
--- /dev/null
+++ b/xsdtest.rng.expected
@@ -0,0 +1 @@
+xsdtest.rng.orig \ No newline at end of file
diff --git a/xsdtest.rng b/xsdtest.rng.orig
index bd760d1..bd760d1 100644
--- a/xsdtest.rng
+++ b/xsdtest.rng.orig
diff --git a/xsdtest.rng.trang b/xsdtest.rng.trang
new file mode 120000
index 0000000..14b5115
--- /dev/null
+++ b/xsdtest.rng.trang
@@ -0,0 +1 @@
+xsdtest.rng.expected \ No newline at end of file