diff --git a/acct-group/cherokee/Manifest b/acct-group/cherokee/Manifest
new file mode 100644
index 0000000..de2701a
--- /dev/null
+++ b/acct-group/cherokee/Manifest
@@ -0,0 +1,2 @@
+EBUILD cherokee-0.ebuild 146 BLAKE2B 9cb29882c4c6aaddb9888d45d2b7ce2e3ee52e5165d71fb2aab1c193bb38e19e8de942253426a5b6bd996f53a384d5d591985d10920b245a47f3e68aea77308f SHA512 f90567df6cf444ebfcbbae7faf22b1a46c842788698a04618b27a9235266d41d1f6ff7dc56cfb385979b6003cc7fbba88e2374d88f2d32c12739138c2b3b5b18
+MISC metadata.xml 252 BLAKE2B f5a02be0647cebbd800d5a51f23ed639c404862318f0f98425a4bdfbc2771859e4349ce81a7b098c31494eef227ddec8c5afa94e665cf034870309742feb96f8 SHA512 df397066e2eefc00d0b64fc66b600b06d948ef9b4e7def258d6615f905a6047c5a11a1e53b6a8ae8fe427e5b99687d60db5aed4aad717455ee9ba5e88990ec09
diff --git a/acct-group/cherokee/cherokee-0.ebuild b/acct-group/cherokee/cherokee-0.ebuild
new file mode 100644
index 0000000..d1802a2
--- /dev/null
+++ b/acct-group/cherokee/cherokee-0.ebuild
@@ -0,0 +1,8 @@
+# Copyright 2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=7
+
+inherit acct-group
+
+ACCT_GROUP_ID=450
diff --git a/acct-group/cherokee/metadata.xml b/acct-group/cherokee/metadata.xml
new file mode 100644
index 0000000..cac949b
--- /dev/null
+++ b/acct-group/cherokee/metadata.xml
@@ -0,0 +1,8 @@
+
+
+
+
+sandino@sandino.net
+Sandino Araico Sánchez
+
+
diff --git a/acct-user/cherokee/Manifest b/acct-user/cherokee/Manifest
new file mode 100644
index 0000000..9e12978
--- /dev/null
+++ b/acct-user/cherokee/Manifest
@@ -0,0 +1,2 @@
+EBUILD cherokee-0.ebuild 269 BLAKE2B 5bcc32b6b5f922ffa588b42783c1caeb5f0b8c658489567ea604a738a329fa1d3f1b38c95b4bb91479aeebf09bf2f95a755804e47ffadaf9b62836aef6ac904f SHA512 160e0312b80881eed2f7e54a4c1610b3e264deb0dcf18da23267ff4888fc09ec0c682860fe2c26941e4f04003086fba1eff3f1cb5aeee84bee8891cbe2a1f58f
+MISC metadata.xml 252 BLAKE2B f5a02be0647cebbd800d5a51f23ed639c404862318f0f98425a4bdfbc2771859e4349ce81a7b098c31494eef227ddec8c5afa94e665cf034870309742feb96f8 SHA512 df397066e2eefc00d0b64fc66b600b06d948ef9b4e7def258d6615f905a6047c5a11a1e53b6a8ae8fe427e5b99687d60db5aed4aad717455ee9ba5e88990ec09
diff --git a/acct-user/cherokee/cherokee-0.ebuild b/acct-user/cherokee/cherokee-0.ebuild
new file mode 100644
index 0000000..1a8ad50
--- /dev/null
+++ b/acct-user/cherokee/cherokee-0.ebuild
@@ -0,0 +1,13 @@
+# Copyright 2019-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=7
+
+inherit acct-user
+
+DESCRIPTION="A user for www-servers/cherokee"
+ACCT_USER_ID=510
+ACCT_USER_GROUPS=( cherokee )
+ACCT_USER_HOME=/var/www
+
+acct-user_add_deps
diff --git a/acct-user/cherokee/metadata.xml b/acct-user/cherokee/metadata.xml
new file mode 100644
index 0000000..cac949b
--- /dev/null
+++ b/acct-user/cherokee/metadata.xml
@@ -0,0 +1,8 @@
+
+
+
+
+sandino@sandino.net
+Sandino Araico Sánchez
+
+
diff --git a/www-servers/cherokee/Manifest b/www-servers/cherokee/Manifest
index 4b45671..66ecc53 100644
--- a/www-servers/cherokee/Manifest
+++ b/www-servers/cherokee/Manifest
@@ -1 +1,10 @@
+AUX cherokee-1.2.104-openssl.patch 4335 BLAKE2B 67bc5e7443450bbaacfb7108a1200f4cf3e24b4b3fc88cc5b977f213a6ed701fc5dd0cee2feb14db6d1c9b1bcfc5a684c7e79ee738aefd6edbaf7046849fda92 SHA512 713897fe099cf99b5295d5125a4ccabf8d4dfdc001d33ee48b894cf23f714873568f2453c307d4336787c4be8e11f197abd74acbb9b7f529591ff3c6e2c8a92d
+AUX cherokee-1.2.104-python.patch 225500 BLAKE2B 38767a99bcfa719a4a8c5436bdcba42a1595848439b6243fc5a1e3dfea02b835096a2955d1dfc7b86dbd4a065b3c3226520272306a2bbcfbc9a2248f38de9610 SHA512 8fc0e0756bef0a7f2deb1672ca81b4fdbe3812271ce69739dabc69ee6c933b3929bc9e4e7490af5988ed9be2b3533f2249e4c7ca8c13a4226245fb549ccb3e77
+AUX cherokee-1.2.99-gentoo.patch 1763 BLAKE2B d4ab785332c60fcac0c22b1b0aefabe44db710a7a0973114e8ddbe0423dd1331c64493b4e862ce944f5afa6da30b307cbd27eead278ea9a080309d4bf1d08cc9 SHA512 2f2b1aa03cd7656ceb90f9a94d24704b4a411b3ab99e084444d1f60ce0270bad650b597b1edbdcb4fb0dda750215f42c3f5de3e9f6253825f3742dfbe871be4b
+AUX cherokee-confd-1.2.98 144 BLAKE2B 9462724ac4500ed501a005e2b001eefa53f2e71e48181a39f497c492cf69cdce1d5d325227535bfdb465b5bb6940cb89aedafb6420a7c14074b15f36b93866e1 SHA512 e010ec9e6d6bd6862c486c494e968cf93ff5f7eeaa523f1bc1fb9db72dc878c17fbc04443e74aef0bbfef1e2ba64ba00a7dc8bbd1b58f6759d2e000c76e88f84
+AUX cherokee-initd-1.2.99 1186 BLAKE2B 3c4d0bbc378940e425bcc8ae4f451e44ded8bc4d4f9d9375c26f6af04c120bccc49c6259aaf33749b071c151b80a096766cc98dee44bfc65af10208b9a43c936 SHA512 595e37b6cf70d4fbd466be918db6c17c37766f5e39a9ee4384a21025f1ce04367aeca495217b1e2c74ed2bce9ff8cfd9a58523b59d1181ce9f470eca97445641
+AUX cherokee.logrotate-r1 255 BLAKE2B 33d6c7dca525e424d1a88c0248595d011215cebae749a5d6aacc9eecd2b6e8d447d67655570baf62b23db96c5f5611d4a98b99bfcb6be9e194b52794264fcf49 SHA512 73be4a46f217302b3e7969f0a5acf7f83357a831331f85c89c17320c594eb9e6fd87da111b7d8eb06840e0b1bb094106a5bb6f7dbf7923e834164a367f85d5ad
+AUX cherokee.service 184 BLAKE2B 76a55b38f5a7d78df80fd57f9461f68dc360b5efafe19338cc242c71f625b6900c5872283c32a97b6234f58621b89616e123983dec027d3bb1f797d388cf743b SHA512 e05cfe0805bcd7f133fc0bf485fbf781b9cc5e6f9e222b8ea89bdc4e9d4531caa6049100670b67d12bdb73c1e93ed5d949cc1828241213cfcd60a39b7424d8ec
DIST cherokee-1.2.104.zip 6200164 BLAKE2B 55ff5b5475c478078727ec072da4f1205209701b972efa420fbee4133cb83cf2ebd4a0f49165e4653c4a6b6f9e0f6ef633258e6ee4732d998b19863e99e6e2f2 SHA512 9c9f44643d0d2636f2e3e61ef8e2918d91d9bb6099be761826c8ffad01d339739ed40984d01151044c2e536d4754b5157d6d20c37627ce49eecdb404a716cd9d
+EBUILD cherokee-1.2.104-r3.ebuild 4991 BLAKE2B b0997ccdcc7efa297db51e0fc8d627931d9d44f1cf1d8b950d935bb2458bdcb29d7b094c674a0670087038ec91dea17964d560f21c75d6ce031f832d5103499f SHA512 7d347ccce321a04dd03f96d128e986189674f0f4faa41f2a19c9f96ba4912eba84e3bf613a738f88169cb33cf8106d2dcdf06f4ef89b1e9cd74ba3f11788e756
+MISC metadata.xml 468 BLAKE2B cbf0b56c17065b7281b4a762cd2390deb3af8c4388c46488d07c0ba39eee46224ee6420075e587345fbaba4a0cfa160daac8fd59b0343dd7cf25d313bee8aac7 SHA512 bfd1c8c432210025dd9b29ec89b383d3211ee9d1aabec4d59c2ec5801feccebcee3c55f7b6ed4e02daab9e72f42ab12517f957f396d0fd410ad3eee4fd05520d
diff --git a/www-servers/cherokee/cherokee-1.2.104-r2.ebuild b/www-servers/cherokee/cherokee-1.2.104-r3.ebuild
similarity index 71%
rename from www-servers/cherokee/cherokee-1.2.104-r2.ebuild
rename to www-servers/cherokee/cherokee-1.2.104-r3.ebuild
index 431f42d..0b69d5d 100644
--- a/www-servers/cherokee/cherokee-1.2.104-r2.ebuild
+++ b/www-servers/cherokee/cherokee-1.2.104-r3.ebuild
@@ -3,11 +3,9 @@
EAPI=7
-WANT_AUTOMAKE="1.11"
+PYTHON_COMPAT=( python3_8 python3_9 python3_10 )
-PYTHON_COMPAT=( python2_7 )
-
-inherit autotools python-r1 pam systemd user
+inherit autotools pam systemd
DESCRIPTION="An extremely fast and tiny web server"
SRC_URI="https://github.com/cherokee/webserver/archive/v${PV}.zip -> ${P}.zip"
@@ -16,12 +14,14 @@ HOMEPAGE="https://www.cherokee-project.com/"
LICENSE="GPL-2"
SLOT="0"
KEYWORDS="~amd64 ~arm ~mips ~ppc ~ppc64 ~x86"
-IUSE="admin ffmpeg debug geoip ipv6 kernel_linux ldap libressl mysql nls pam php rrdtool ssl static static-libs"
+IUSE="ffmpeg debug geoip ipv6 kernel_linux ldap mysql nls pam php ssl rrdtool static static-libs threads"
RDEPEND=""
REQUIRED_USE="${PYTHON_REQUIRED_USE}"
COMMON_DEPEND="
${PYTHON_DEPS}
+ acct-group/cherokee
+ acct-user/cherokee
dev-libs/libpcre
>=sys-libs/zlib-1.1.4-r1
ffmpeg? ( media-video/ffmpeg )
@@ -34,13 +34,11 @@ COMMON_DEPEND="
dev-lang/php:*[fpm]
dev-lang/php:*[cgi]
) )
- ssl? (
- !libressl? ( po/admin/POTFILES.in
@@ -68,11 +65,12 @@ src_prepare() {
src_configure() {
local myconf
- if use admin ; then
- myconf="${myconf} --enable-admin --with-python=/usr/bin/python"
- else
+# Disable broken admin
+# if use admin ; then
+# myconf="${myconf} --enable-admin --with-python=/usr/bin/python"
+# else
myconf="${myconf} --disable-admin"
- fi
+# fi
# Uses autodetect because --with-php requires path to php-{fpm,cgi}.
if ! use php ; then
@@ -137,13 +135,13 @@ src_install() {
newinitd "${FILESDIR}/${PN}-initd-1.2.99" ${PN}
newconfd "${FILESDIR}/${PN}-confd-1.2.98" ${PN}
- if ! use admin ; then
+# if ! use admin ; then
rm -r \
"${ED}"/usr/bin/cherokee-admin-launcher \
"${ED}"/usr/bin/CTK-run \
"${ED}"/usr/sbin/cherokee-admin \
"${ED}"/usr/share/cherokee/admin || die
- fi
+# fi
exeinto /usr/share/doc/${PF}/contrib
doexe contrib/{bin2buffer.py,make-cert.sh,make-dh_params.sh,tracelor.py}
@@ -175,22 +173,23 @@ src_install() {
pkg_postinst() {
elog
- if use admin ; then
- elog "Just run '/usr/sbin/cherokee-admin' and go to: http://localhost:9090"
- elog
- elog "Cherokee currently supports configuration versioning, so from now on,"
- elog "whenever a change is made to the configuration file format,"
- elog "Cherokee-Admin will be able to automatically convert yours to the new"
- elog "release. You simply have to load Cherokee-Admin and it will be converted"
- elog "once you proceed to saving it."
- elog
- elog "There is also a command line utility that you can use to do the exact"
- elog "same thing. Config format can change in different versions. It is"
- elog "provided under:"
- elog " ${EPREFIX}/usr/share/cherokee/admin/upgrade_config.py"
- else
- elog "Try USE=admin if you want an easy way to configure cherokee."
- fi
+# Disable broken admin
+# if use admin ; then
+# elog "Just run '/usr/sbin/cherokee-admin' and go to: http://localhost:9090"
+# elog
+# elog "Cherokee currently supports configuration versioning, so from now on,"
+# elog "whenever a change is made to the configuration file format,"
+# elog "Cherokee-Admin will be able to automatically convert yours to the new"
+# elog "release. You simply have to load Cherokee-Admin and it will be converted"
+# elog "once you proceed to saving it."
+# elog
+# elog "There is also a command line utility that you can use to do the exact"
+# elog "same thing. Config format can change in different versions. It is"
+# elog "provided under:"
+# elog " ${EPREFIX}/usr/share/cherokee/admin/upgrade_config.py"
+# else
+# elog "Try USE=admin if you want an easy way to configure cherokee."
+# fi
elog
elog "emerge www-servers/spawn-fcgi if you use Ruby on Rails with ${PN}."
elog
diff --git a/www-servers/cherokee/files/cherokee-1.2.104-openssl.patch b/www-servers/cherokee/files/cherokee-1.2.104-openssl.patch
new file mode 100644
index 0000000..af9a9d5
--- /dev/null
+++ b/www-servers/cherokee/files/cherokee-1.2.104-openssl.patch
@@ -0,0 +1,95 @@
+diff -puriN webserver-1.2.104.orig/cherokee/cryptor_libssl.c webserver-1.2.104/cherokee/cryptor_libssl.c
+--- webserver-1.2.104.orig/cherokee/cryptor_libssl.c 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/cryptor_libssl.c 2022-03-18 05:44:45.000000000 -0600
+@@ -238,13 +238,13 @@ cherokee_cryptor_libssl_find_vserver (SS
+ /* SSL_set_SSL_CTX() only change certificates. We need to
+ * changes more options by hand.
+ */
+- SSL_set_options(ssl, SSL_CTX_get_options(ssl->ctx));
++ SSL_set_options(ssl, SSL_CTX_get_options(SSL_get_SSL_CTX(ssl)));
+
+ if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) ||
+ (SSL_num_renegotiations(ssl) == 0)) {
+
+- SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ssl->ctx),
+- SSL_CTX_get_verify_callback(ssl->ctx));
++ SSL_set_verify(ssl, SSL_CTX_get_verify_mode(SSL_get_SSL_CTX(ssl)),
++ SSL_CTX_get_verify_callback(SSL_get_SSL_CTX(ssl)));
+ }
+
+ return ret_ok;
+@@ -792,9 +792,7 @@ _socket_init_tls (cherokee_cryptor_socke
+
+ /* Disable Ciphers renegotiation (CVE-2009-3555)
+ */
+- if (cryp->session->s3) {
+- cryp->session->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
+- }
++ SSL_set_options(cryp->session, SSL_OP_NO_RENEGOTIATION);
+
+ return ret_ok;
+ }
+diff -puriN webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_1024.c webserver-1.2.104/cherokee/cryptor_libssl_dh_1024.c
+--- webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_1024.c 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/cryptor_libssl_dh_1024.c 2022-03-18 05:08:11.000000000 -0600
+@@ -23,9 +23,9 @@ static DH *get_dh1024()
+ DH *dh;
+
+ if ((dh=DH_new()) == NULL) return(NULL);
+- dh->p=BN_bin2bn(dh1024_p,sizeof(dh1024_p),NULL);
+- dh->g=BN_bin2bn(dh1024_g,sizeof(dh1024_g),NULL);
+- if ((dh->p == NULL) || (dh->g == NULL)) {
++ DH_set0_pqg(dh, BN_bin2bn(dh1024_p,sizeof(dh1024_p),NULL),
++ NULL, BN_bin2bn(dh1024_g,sizeof(dh1024_g),NULL));
++ if ((DH_get0_p(dh) == NULL) || (DH_get0_g(dh) == NULL)) {
+ DH_free(dh); return(NULL);
+ }
+ return(dh);
+diff -puriN webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_2048.c webserver-1.2.104/cherokee/cryptor_libssl_dh_2048.c
+--- webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_2048.c 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/cryptor_libssl_dh_2048.c 2022-03-18 05:09:20.000000000 -0600
+@@ -34,9 +34,9 @@ static DH *get_dh2048()
+ DH *dh;
+
+ if ((dh=DH_new()) == NULL) return(NULL);
+- dh->p=BN_bin2bn(dh2048_p,sizeof(dh2048_p),NULL);
+- dh->g=BN_bin2bn(dh2048_g,sizeof(dh2048_g),NULL);
+- if ((dh->p == NULL) || (dh->g == NULL)) {
++ DH_set0_pqg(dh, BN_bin2bn(dh2048_p,sizeof(dh2048_p),NULL),
++ NULL, BN_bin2bn(dh2048_g,sizeof(dh2048_g),NULL));
++ if ((DH_get0_p(dh) == NULL) || (DH_get0_g(dh) == NULL)) {
+ DH_free(dh); return(NULL);
+ }
+ return(dh);
+diff -puriN webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_4096.c webserver-1.2.104/cherokee/cryptor_libssl_dh_4096.c
+--- webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_4096.c 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/cryptor_libssl_dh_4096.c 2022-03-18 05:10:34.000000000 -0600
+@@ -55,9 +55,9 @@ static DH *get_dh4096()
+ DH *dh;
+
+ if ((dh=DH_new()) == NULL) return(NULL);
+- dh->p=BN_bin2bn(dh4096_p,sizeof(dh4096_p),NULL);
+- dh->g=BN_bin2bn(dh4096_g,sizeof(dh4096_g),NULL);
+- if ((dh->p == NULL) || (dh->g == NULL)) {
++ DH_set0_pqg(dh, BN_bin2bn(dh4096_p,sizeof(dh4096_p),NULL),
++ NULL, BN_bin2bn(dh4096_g,sizeof(dh4096_g),NULL));
++ if ((DH_get0_p(dh) == NULL) || (DH_get0_g(dh) == NULL)) {
+ DH_free(dh); return(NULL);
+ }
+ return(dh);
+diff -puriN webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_512.c webserver-1.2.104/cherokee/cryptor_libssl_dh_512.c
+--- webserver-1.2.104.orig/cherokee/cryptor_libssl_dh_512.c 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/cryptor_libssl_dh_512.c 2022-03-18 05:05:46.000000000 -0600
+@@ -18,9 +18,9 @@ static DH *get_dh512()
+ DH *dh;
+
+ if ((dh=DH_new()) == NULL) return(NULL);
+- dh->p=BN_bin2bn(dh512_p,sizeof(dh512_p),NULL);
+- dh->g=BN_bin2bn(dh512_g,sizeof(dh512_g),NULL);
+- if ((dh->p == NULL) || (dh->g == NULL)) {
++ DH_set0_pqg(dh, BN_bin2bn(dh512_p,sizeof(dh512_p),NULL),
++ NULL, BN_bin2bn(dh512_g,sizeof(dh512_g),NULL));
++ if ((DH_get0_p(dh) == NULL) || (DH_get0_g(dh) == NULL)) {
+ DH_free(dh); return(NULL);
+ }
+ return(dh);
diff --git a/www-servers/cherokee/files/cherokee-1.2.104-python.patch b/www-servers/cherokee/files/cherokee-1.2.104-python.patch
new file mode 100644
index 0000000..1d607a4
--- /dev/null
+++ b/www-servers/cherokee/files/cherokee-1.2.104-python.patch
@@ -0,0 +1,5429 @@
+diff -puriN webserver-1.2.104.orig/cherokee/errors.py webserver-1.2.104/cherokee/errors.py
+--- webserver-1.2.104.orig/cherokee/errors.py 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/cherokee/errors.py 2022-03-18 04:46:29.000000000 -0600
+@@ -12,7 +12,7 @@ import os, re, sys
+ SOURCE_DIRS = ['.', '../cget']
+
+ #
+-# Error reading
++# Error reading
+ #
+ class CherokeeError:
+ def __init__ (self, **kwargs):
+@@ -33,7 +33,7 @@ def e (error_id, title, **kwargs):
+ # Check dup. errors
+ for err in _errors:
+ if error_id == err.id:
+- raise ValueError, "ERROR: Duplicated error %s" %(error_id)
++ raise ValueError("ERROR: Duplicated error %s" %(error_id))
+
+ # New error
+ kwargs['id'] = error_id
+@@ -80,13 +80,13 @@ def check_source_code (dirs):
+ found = True
+ break
+ if not found:
+- print >> sys.stderr, "Undefined Error: CHEROKEE_ERROR_%s, used in %s" % (s, errors_seen[s])
++ print ("Undefined Error: CHEROKEE_ERROR_%s, used in %s" % (s, errors_seen[s]), file=sys.stderr)
+ error_found = True
+
+ # Unused errors in the definition file
+ for def_e in _errors:
+ if not def_e.__seen_in_grep:
+- print >> sys.stderr, "Unused Error: CHEROKEE_ERROR_%s" % (def_e.id)
++ print ("Unused Error: CHEROKEE_ERROR_%s" % (def_e.id), file=sys.stderr)
+ error_found = True
+
+ return error_found
+@@ -134,8 +134,11 @@ def check_parameters (dirs):
+ break
+ for param in internal_params:
+ tmp = tmp.replace(param, '')
+-
+- params_num = len (filter (lambda x: len(x), tmp.split(',')))
++
++ params_filter = filter (lambda x: len(x), tmp.split(','))
++ params_num = 0
++ for item in params_filter :
++ params_num += 1
+ source_errors_params[error] = params_num
+
+ # Compare both
+@@ -145,7 +148,7 @@ def check_parameters (dirs):
+ source_num = source_errors_params[error.id]
+ known_num = known_errors_params[error.id]
+ if source_num != known_num:
+- print >> sys.stderr, "ERROR: Parameter number mismatch: %s (source %d, definition %d)" % (error.id, source_num, known_num)
++ print ("ERROR: Parameter number mismatch: %s (source %d, definition %d)" % (error.id, source_num, known_num), file=sys.stderr)
+ error_found = True
+
+ return error_found
+@@ -243,14 +246,14 @@ def main():
+ error = True
+
+ if error:
+- print "USAGE:"
+- print
+- print " * Create the definitions file:"
+- print " %s [--skip-tests] --defines output_file" %(sys.argv[0])
+- print
+- print " * Create the error list file:"
+- print " %s [--skip-tests] --errors output_file" %(sys.argv[0])
+- print
++ print("USAGE:")
++ print("")
++ print(" * Create the definitions file:")
++ print(" %s [--skip-tests] --defines output_file" %(sys.argv[0]))
++ print("")
++ print(" * Create the error list file:")
++ print(" %s [--skip-tests] --errors output_file" %(sys.argv[0]))
++ print("")
+ sys.exit(1)
+
+ # Perform
+diff -puriN webserver-1.2.104.orig/doc/build/asciidoc.py webserver-1.2.104/doc/build/asciidoc.py
+--- webserver-1.2.104.orig/doc/build/asciidoc.py 2014-04-01 11:12:48.000000000 -0600
++++ webserver-1.2.104/doc/build/asciidoc.py 1969-12-31 18:00:00.000000000 -0600
+@@ -1,5312 +0,0 @@
+-#!/usr/bin/env python2
+-"""
+-asciidoc - converts an AsciiDoc text file to DocBook, HTML or LinuxDoc
+-
+-Copyright (C) 2002-2009 Stuart Rackham. Free use of this software is granted
+-under the terms of the GNU General Public License (GPL).
+-"""
+-
+-import sys, os, re, time, traceback, tempfile, subprocess, codecs, locale
+-
+-### Used by asciidocapi.py ###
+-VERSION = '8.4.5' # See CHANGLOG file for version history.
+-
+-MIN_PYTHON_VERSION = 2.4 # Require this version of Python or better.
+-
+-#---------------------------------------------------------------------------
+-# Program constants.
+-#---------------------------------------------------------------------------
+-DEFAULT_BACKEND = 'xhtml11'
+-DEFAULT_DOCTYPE = 'article'
+-# Allowed substitution options for List, Paragraph and DelimitedBlock
+-# definition subs entry.
+-SUBS_OPTIONS = ('specialcharacters','quotes','specialwords',
+- 'replacements', 'attributes','macros','callouts','normal','verbatim',
+- 'none','replacements2')
+-# Default value for unspecified subs and presubs configuration file entries.
+-SUBS_NORMAL = ('specialcharacters','quotes','attributes',
+- 'specialwords','replacements','macros','replacements2')
+-SUBS_VERBATIM = ('specialcharacters','callouts')
+-
+-NAME_RE = r'(?u)[^\W\d][-\w]*' # Valid section or attrbibute name.
+-
+-
+-#---------------------------------------------------------------------------
+-# Utility functions and classes.
+-#---------------------------------------------------------------------------
+-
+-class EAsciiDoc(Exception): pass
+-
+-class OrderedDict(dict):
+- """
+- Dictionary ordered by insertion order.
+- Python Cookbook: Ordered Dictionary, Submitter: David Benjamin.
+- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
+- """
+- def __init__(self, d=None, **kwargs):
+- self._keys = []
+- if d is None: d = kwargs
+- dict.__init__(self, d)
+- def __delitem__(self, key):
+- dict.__delitem__(self, key)
+- self._keys.remove(key)
+- def __setitem__(self, key, item):
+- dict.__setitem__(self, key, item)
+- if key not in self._keys: self._keys.append(key)
+- def clear(self):
+- dict.clear(self)
+- self._keys = []
+- def copy(self):
+- d = dict.copy(self)
+- d._keys = self._keys[:]
+- return d
+- def items(self):
+- return zip(self._keys, self.values())
+- def keys(self):
+- return self._keys
+- def popitem(self):
+- try:
+- key = self._keys[-1]
+- except IndexError:
+- raise KeyError('dictionary is empty')
+- val = self[key]
+- del self[key]
+- return (key, val)
+- def setdefault(self, key, failobj = None):
+- dict.setdefault(self, key, failobj)
+- if key not in self._keys: self._keys.append(key)
+- def update(self, d=None, **kwargs):
+- if d is None:
+- d = kwargs
+- dict.update(self, d)
+- for key in d.keys():
+- if key not in self._keys: self._keys.append(key)
+- def values(self):
+- return map(self.get, self._keys)
+-
+-class AttrDict(dict):
+- """
+- Like a dictionary except values can be accessed as attributes i.e. obj.foo
+- can be used in addition to obj['foo'].
+- If an item is not present None is returned.
+- """
+- def __getattr__(self, key):
+- try: return self[key]
+- except KeyError, k: return None
+- def __setattr__(self, key, value):
+- self[key] = value
+- def __delattr__(self, key):
+- try: del self[key]
+- except KeyError, k: raise AttributeError, k
+- def __repr__(self):
+- return ''
+- def __getstate__(self):
+- return dict(self)
+- def __setstate__(self,value):
+- for k,v in value.items(): self[k]=v
+-
+-class Trace(object):
+- """
+- Used in conjunction with the 'trace' attribute to generate diagnostic
+- output. There is a single global instance of this class named trace.
+- """
+- SUBS_NAMES = ('specialcharacters','quotes','specialwords',
+- 'replacements', 'attributes','macros','callouts',
+- 'replacements2')
+- def __init__(self):
+- self.name_re = '' # Regexp pattern to match trace names.
+- self.linenos = True
+- self.offset = 0
+- def __call__(self, name, before, after=None):
+- """
+- Print trace message if tracing is on and the trace 'name' matches the
+- document 'trace' attribute (treated as a regexp).
+- The 'before' and 'after' messages are only printed if they differ.
+- """
+- name_re = document.attributes.get('trace')
+- if name_re == 'subs': # Alias for all the inline substitutions.
+- name_re = '|'.join(self.SUBS_NAMES)
+- self.name_re = name_re
+- if self.name_re is not None:
+- msg = message.format(name, 'TRACE: ', self.linenos, offset=self.offset)
+- if before != after and re.match(self.name_re,name):
+- if is_array(before):
+- before = '\n'.join(before)
+- if after is None:
+- msg += '\n%s\n' % before
+- else:
+- if is_array(after):
+- after = '\n'.join(after)
+- msg += '\n<<<\n%s\n>>>\n%s\n' % (before,after)
+- message.stderr(msg)
+-
+-class Message:
+- """
+- Message functions.
+- """
+- def __init__(self):
+- self.linenos = None # Used to globally override line numbers.
+- self.messages = []
+-
+- def stderr(self,line=''):
+- self.messages.append(line)
+- if __name__ == '__main__':
+- sys.stderr.write(line+os.linesep)
+-
+- def verbose(self, msg,linenos=True):
+- if config.verbose:
+- msg = self.format(msg,linenos=linenos)
+- self.stderr(msg)
+-
+- def warning(self, msg,linenos=True,offset=0):
+- msg = self.format(msg,'WARNING: ',linenos,offset=offset)
+- document.has_warnings = True
+- self.stderr(msg)
+-
+- def deprecated(self, msg, linenos=True):
+- msg = self.format(msg, 'DEPRECATED: ', linenos)
+- self.stderr(msg)
+-
+- def format(self, msg, prefix='', linenos=True, cursor=None, offset=0):
+- """Return formatted message string."""
+- if self.linenos is not False and ((linenos or self.linenos) and reader.cursor):
+- if cursor is None:
+- cursor = reader.cursor
+- prefix += '%s: line %d: ' % (os.path.basename(cursor[0]),cursor[1]+offset)
+- return prefix + msg
+-
+- def error(self, msg, cursor=None, halt=False):
+- """
+- Report fatal error.
+- If halt=True raise EAsciiDoc exception.
+- If halt=False don't exit application, continue in the hope of reporting
+- all fatal errors finishing with a non-zero exit code.
+- """
+- if halt:
+- raise EAsciiDoc, self.format(msg,linenos=False,cursor=cursor)
+- else:
+- msg = self.format(msg,'ERROR: ',cursor=cursor)
+- self.stderr(msg)
+- document.has_errors = True
+-
+- def unsafe(self, msg):
+- self.error('unsafe: '+msg)
+-
+-
+-def file_in(fname, directory):
+- """Return True if file fname resides inside directory."""
+- assert os.path.isfile(fname)
+- # Empty directory (not to be confused with None) is the current directory.
+- if directory == '':
+- directory = os.getcwd()
+- else:
+- assert os.path.isdir(directory)
+- directory = os.path.realpath(directory)
+- fname = os.path.realpath(fname)
+- return os.path.commonprefix((directory, fname)) == directory
+-
+-def safe():
+- return document.safe
+-
+-def is_safe_file(fname, directory=None):
+- # A safe file must reside in directory directory (defaults to the source
+- # file directory).
+- if directory is None:
+- if document.infile == '':
+- return not safe()
+- directory = os.path.dirname(document.infile)
+- elif directory == '':
+- directory = '.'
+- return not safe() or file_in(fname, directory)
+-
+-def safe_filename(fname, parentdir):
+- """
+- Return file name which must reside in the parent file directory.
+- Return None if file is not found or not safe.
+- """
+- if not os.path.isabs(fname):
+- # Include files are relative to parent document
+- # directory.
+- fname = os.path.join(parentdir,fname)
+- if not os.path.isfile(fname):
+- message.warning('include file not found: %s' % fname)
+- return None
+- if not is_safe_file(fname, parentdir):
+- message.unsafe('include file: %s' % fname)
+- return None
+- return fname
+-
+-def assign(dst,src):
+- """Assign all attributes from 'src' object to 'dst' object."""
+- for a,v in src.__dict__.items():
+- setattr(dst,a,v)
+-
+-def strip_quotes(s):
+- """Trim white space and, if necessary, quote characters from s."""
+- s = s.strip()
+- # Strip quotation mark characters from quoted strings.
+- if len(s) >= 3 and s[0] == '"' and s[-1] == '"':
+- s = s[1:-1]
+- return s
+-
+-def is_re(s):
+- """Return True if s is a valid regular expression else return False."""
+- try: re.compile(s)
+- except: return False
+- else: return True
+-
+-def re_join(relist):
+- """Join list of regular expressions re1,re2,... to single regular
+- expression (re1)|(re2)|..."""
+- if len(relist) == 0:
+- return None
+- result = []
+- # Delete named groups to avoid ambiguity.
+- for s in relist:
+- result.append(re.sub(r'\?P<\S+?>','',s))
+- result = ')|('.join(result)
+- result = '('+result+')'
+- return result
+-
+-def validate(value,rule,errmsg):
+- """Validate value against rule expression. Throw EAsciiDoc exception with
+- errmsg if validation fails."""
+- try:
+- if not eval(rule.replace('$',str(value))):
+- raise EAsciiDoc,errmsg
+- except Exception:
+- raise EAsciiDoc,errmsg
+- return value
+-
+-def lstrip_list(s):
+- """
+- Return list with empty items from start of list removed.
+- """
+- for i in range(len(s)):
+- if s[i]: break
+- else:
+- return []
+- return s[i:]
+-
+-def rstrip_list(s):
+- """
+- Return list with empty items from end of list removed.
+- """
+- for i in range(len(s)-1,-1,-1):
+- if s[i]: break
+- else:
+- return []
+- return s[:i+1]
+-
+-def strip_list(s):
+- """
+- Return list with empty items from start and end of list removed.
+- """
+- s = lstrip_list(s)
+- s = rstrip_list(s)
+- return s
+-
+-def is_array(obj):
+- """
+- Return True if object is list or tuple type.
+- """
+- return isinstance(obj,list) or isinstance(obj,tuple)
+-
+-def dovetail(lines1, lines2):
+- """
+- Append list or tuple of strings 'lines2' to list 'lines1'. Join the last
+- non-blank item in 'lines1' with the first non-blank item in 'lines2' into a
+- single string.
+- """
+- assert is_array(lines1)
+- assert is_array(lines2)
+- lines1 = strip_list(lines1)
+- lines2 = strip_list(lines2)
+- if not lines1 or not lines2:
+- return list(lines1) + list(lines2)
+- result = list(lines1[:-1])
+- result.append(lines1[-1] + lines2[0])
+- result += list(lines2[1:])
+- return result
+-
+-def dovetail_tags(stag,content,etag):
+- """Merge the end tag with the first content line and the last
+- content line with the end tag. This ensures verbatim elements don't
+- include extraneous opening and closing line breaks."""
+- return dovetail(dovetail(stag,content), etag)
+-
+-def parse_attributes(attrs,dict):
+- """Update a dictionary with name/value attributes from the attrs string.
+- The attrs string is a comma separated list of values and keyword name=value
+- pairs. Values must preceed keywords and are named '1','2'... The entire
+- attributes list is named '0'. If keywords are specified string values must
+- be quoted. Examples:
+-
+- attrs: ''
+- dict: {}
+-
+- attrs: 'hello,world'
+- dict: {'2': 'world', '0': 'hello,world', '1': 'hello'}
+-
+- attrs: '"hello", planet="earth"'
+- dict: {'planet': 'earth', '0': '"hello",planet="earth"', '1': 'hello'}
+- """
+- def f(*args,**keywords):
+- # Name and add aguments '1','2'... to keywords.
+- for i in range(len(args)):
+- if not str(i+1) in keywords:
+- keywords[str(i+1)] = args[i]
+- return keywords
+-
+- if not attrs:
+- return
+- dict['0'] = attrs
+- # Replace line separators with spaces so line spanning works.
+- s = re.sub(r'\s', ' ', attrs)
+- try:
+- d = eval('f('+s+')')
+- # Attributes must evaluate to strings, numbers or None.
+- for v in d.values():
+- if not (isinstance(v,str) or isinstance(v,int) or isinstance(v,float) or v is None):
+- raise
+- except Exception:
+- s = s.replace('"','\\"')
+- s = s.split(',')
+- s = map(lambda x: '"' + x.strip() + '"', s)
+- s = ','.join(s)
+- try:
+- d = eval('f('+s+')')
+- except Exception:
+- return # If there's a syntax error leave with {0}=attrs.
+- for k in d.keys(): # Drop any empty positional arguments.
+- if d[k] == '': del d[k]
+- dict.update(d)
+- assert len(d) > 0
+-
+-def parse_named_attributes(s,attrs):
+- """Update a attrs dictionary with name="value" attributes from the s string.
+- Returns False if invalid syntax.
+- Example:
+- attrs: 'star="sun",planet="earth"'
+- dict: {'planet':'earth', 'star':'sun'}
+- """
+- def f(**keywords): return keywords
+-
+- try:
+- d = eval('f('+s+')')
+- attrs.update(d)
+- return True
+- except Exception:
+- return False
+-
+-def parse_list(s):
+- """Parse comma separated string of Python literals. Return a tuple of of
+- parsed values."""
+- try:
+- result = eval('tuple(['+s+'])')
+- except Exception:
+- raise EAsciiDoc,'malformed list: '+s
+- return result
+-
+-def parse_options(options,allowed,errmsg):
+- """Parse comma separated string of unquoted option names and return as a
+- tuple of valid options. 'allowed' is a list of allowed option values.
+- If allowed=() then all legitimate names are allowed.
+- 'errmsg' is an error message prefix if an illegal option error is thrown."""
+- result = []
+- if options:
+- for s in re.split(r'\s*,\s*',options):
+- if (allowed and s not in allowed) or not is_name(s):
+- raise EAsciiDoc,'%s: %s' % (errmsg,s)
+- result.append(s)
+- return tuple(result)
+-
+-def symbolize(s):
+- """Drop non-symbol characters and convert to lowercase."""
+- return re.sub(r'(?u)[^\w\-_]', '', s).lower()
+-
+-def is_name(s):
+- """Return True if s is valid attribute, macro or tag name
+- (starts with alpha containing alphanumeric and dashes only)."""
+- return re.match(r'^'+NAME_RE+r'$',s) is not None
+-
+-def subs_quotes(text):
+- """Quoted text is marked up and the resulting text is
+- returned."""
+- keys = config.quotes.keys()
+- for q in keys:
+- i = q.find('|')
+- if i != -1 and q != '|' and q != '||':
+- lq = q[:i] # Left quote.
+- rq = q[i+1:] # Right quote.
+- else:
+- lq = rq = q
+- tag = config.quotes[q]
+- # Unconstrained quotes prefix the tag name with a hash.
+- if tag[0] == '#':
+- tag = tag[1:]
+- # Unconstrained quotes can appear anywhere.
+- reo = re.compile(r'(?msu)(^|.)(\[(?P[^[\]]+?)\])?' \
+- + r'(?:' + re.escape(lq) + r')' \
+- + r'(?P.+?)(?:'+re.escape(rq)+r')')
+- else:
+- # The text within constrained quotes must be bounded by white space.
+- # Non-word (\W) characters are allowed at boundaries to accomodate
+- # enveloping quotes.
+- reo = re.compile(r'(?msu)(^|\W)(\[(?P[^[\]]+?)\])?' \
+- + r'(?:' + re.escape(lq) + r')' \
+- + r'(?P\S|\S.*?\S)(?:'+re.escape(rq)+r')(?=\W|$)')
+- pos = 0
+- while True:
+- mo = reo.search(text,pos)
+- if not mo: break
+- if text[mo.start()] == '\\':
+- # Delete leading backslash.
+- text = text[:mo.start()] + text[mo.start()+1:]
+- # Skip past start of match.
+- pos = mo.start() + 1
+- else:
+- attrlist = {}
+- parse_attributes(mo.group('attrlist'), attrlist)
+- stag,etag = config.tag(tag, attrlist)
+- s = mo.group(1) + stag + mo.group('content') + etag
+- text = text[:mo.start()] + s + text[mo.end():]
+- pos = mo.start() + len(s)
+- return text
+-
+-def subs_tag(tag,dict={}):
+- """Perform attribute substitution and split tag string returning start, end
+- tag tuple (c.f. Config.tag())."""
+- if not tag:
+- return [None,None]
+- s = subs_attrs(tag,dict)
+- if not s:
+- message.warning('tag \'%s\' dropped: contains undefined attribute' % tag)
+- return [None,None]
+- result = s.split('|')
+- if len(result) == 1:
+- return result+[None]
+- elif len(result) == 2:
+- return result
+- else:
+- raise EAsciiDoc,'malformed tag: %s' % tag
+-
+-def parse_entry(entry, dict=None, unquote=False, unique_values=False,
+- allow_name_only=False, escape_delimiter=True):
+- """Parse name=value entry to dictionary 'dict'. Return tuple (name,value)
+- or None if illegal entry.
+- If name= then value is set to ''.
+- If name and allow_name_only=True then value is set to ''.
+- If name! and allow_name_only=True then value is set to None.
+- Leading and trailing white space is striped from 'name' and 'value'.
+- 'name' can contain any printable characters.
+- If the '=' delimiter character is allowed in the 'name' then
+- it must be escaped with a backslash and escape_delimiter must be True.
+- If 'unquote' is True leading and trailing double-quotes are stripped from
+- 'name' and 'value'.
+- If unique_values' is True then dictionary entries with the same value are
+- removed before the parsed entry is added."""
+- if escape_delimiter:
+- mo = re.search(r'(?:[^\\](=))',entry)
+- else:
+- mo = re.search(r'(=)',entry)
+- if mo: # name=value entry.
+- if mo.group(1):
+- name = entry[:mo.start(1)]
+- if escape_delimiter:
+- name = name.replace(r'\=','=') # Unescape \= in name.
+- value = entry[mo.end(1):]
+- elif allow_name_only and entry: # name or name! entry.
+- name = entry
+- if name[-1] == '!':
+- name = name[:-1]
+- value = None
+- else:
+- value = ''
+- else:
+- return None
+- if unquote:
+- name = strip_quotes(name)
+- if value is not None:
+- value = strip_quotes(value)
+- else:
+- name = name.strip()
+- if value is not None:
+- value = value.strip()
+- if not name:
+- return None
+- if dict is not None:
+- if unique_values:
+- for k,v in dict.items():
+- if v == value: del dict[k]
+- dict[name] = value
+- return name,value
+-
+-def parse_entries(entries, dict, unquote=False, unique_values=False,
+- allow_name_only=False,escape_delimiter=True):
+- """Parse name=value entries from from lines of text in 'entries' into
+- dictionary 'dict'. Blank lines are skipped."""
+- entries = config.expand_templates(entries)
+- for entry in entries:
+- if entry and not parse_entry(entry, dict, unquote, unique_values,
+- allow_name_only, escape_delimiter):
+- raise EAsciiDoc,'malformed section entry: %s' % entry
+-
+-def load_conf_file(sections, fname, dir, namepat=NAME_RE):
+- """Loads sections dictionary with sections from file fname.
+- Existing sections are overlaid. Silently skips missing configuration
+- files."""
+- if dir:
+- fname = os.path.join(dir, fname)
+- # Sliently skip missing configuration file.
+- if not os.path.isfile(fname):
+- return
+- reo = re.compile(r'^\[(?P'+namepat+')\]\s*$')
+- section,contents = '',[]
+- for line in open(fname):
+- if line and line[0] == '#': # Skip comment lines.
+- continue
+- line = line.rstrip()
+- found = reo.findall(line)
+- if found:
+- if section: # Store previous section.
+- sections[section] = contents
+- section = found[0].lower()
+- contents = []
+- else:
+- contents.append(line)
+- if section and contents: # Store last section.
+- sections[section] = contents
+-
+-def dump_section(name,dict,f=sys.stdout):
+- """Write parameters in 'dict' as in configuration file section format with
+- section 'name'."""
+- f.write('[%s]%s' % (name,writer.newline))
+- for k,v in dict.items():
+- k = str(k)
+- k = k.replace('=',r'\=') # Escape = in name.
+- # Quote if necessary.
+- if len(k) != len(k.strip()):
+- k = '"'+k+'"'
+- if v and len(v) != len(v.strip()):
+- v = '"'+v+'"'
+- if v is None:
+- # Don't dump undefined attributes.
+- continue
+- else:
+- s = k+'='+v
+- if s[0] == '#':
+- s = '\\' + s # Escape so not treated as comment lines.
+- f.write('%s%s' % (s,writer.newline))
+- f.write(writer.newline)
+-
+-def update_attrs(attrs,dict):
+- """Update 'attrs' dictionary with parsed attributes in dictionary 'dict'."""
+- for k,v in dict.items():
+- if not is_name(k):
+- raise EAsciiDoc,'illegal attribute name: %s' % k
+- attrs[k] = v
+-
+-def filter_lines(filter_cmd, lines, attrs={}):
+- """
+- Run 'lines' through the 'filter_cmd' shell command and return the result.
+- The 'attrs' dictionary contains additional filter attributes.
+- """
+- def findfilter(name,dir,filter):
+- """Find filter file 'fname' with style name 'name' in directory
+- 'dir'. Return found file path or None if not found."""
+- if name:
+- result = os.path.join(dir,'filters',name,filter)
+- if os.path.isfile(result):
+- return result
+- result = os.path.join(dir,'filters',filter)
+- if os.path.isfile(result):
+- return result
+- return None
+-
+- # Return input lines if there's not filter.
+- if not filter_cmd or not filter_cmd.strip():
+- return lines
+- # Perform attributes substitution on the filter command.
+- s = subs_attrs(filter_cmd, attrs)
+- if not s:
+- raise EAsciiDoc,'undefined filter attribute in command: %s' % filter_cmd
+- filter_cmd = s.strip()
+- # Parse for quoted and unquoted command and command tail.
+- # Double quoted.
+- mo = re.match(r'^"(?P[^"]+)"(?P.*)$', filter_cmd)
+- if not mo:
+- # Single quoted.
+- mo = re.match(r"^'(?P[^']+)'(?P.*)$", filter_cmd)
+- if not mo:
+- # Unquoted catch all.
+- mo = re.match(r'^(?P\S+)(?P.*)$', filter_cmd)
+- cmd = mo.group('cmd').strip()
+- found = None
+- if not os.path.dirname(cmd):
+- # Filter command has no directory path so search filter directories.
+- filtername = attrs.get('style')
+- if USER_DIR:
+- found = findfilter(filtername, USER_DIR, cmd)
+- if not found:
+- found = findfilter(filtername, CONF_DIR, cmd)
+- if not found:
+- found = findfilter(filtername, APP_DIR, cmd)
+- else:
+- if os.path.isfile(cmd):
+- found = cmd
+- else:
+- message.warning('filter not found: %s' % cmd)
+- if found:
+- filter_cmd = '"' + found + '"' + mo.group('tail')
+- if sys.platform == 'win32':
+- # Windows doesn't like running scripts directly so explicitly
+- # specify interpreter.
+- if found:
+- if cmd.endswith('.py'):
+- filter_cmd = 'python ' + filter_cmd
+- elif cmd.endswith('.rb'):
+- filter_cmd = 'ruby ' + filter_cmd
+- message.verbose('filtering: ' + filter_cmd)
+- try:
+- p = subprocess.Popen(filter_cmd, shell=True,
+- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+- output = p.communicate(os.linesep.join(lines))[0]
+- except Exception:
+- raise EAsciiDoc,'filter error: %s: %s' % (filter_cmd, sys.exc_info()[1])
+- if output:
+- result = [s.rstrip() for s in output.split(os.linesep)]
+- else:
+- result = []
+- filter_status = p.wait()
+- if filter_status:
+- message.warning('filter non-zero exit code: %s: returned %d' %
+- (filter_cmd, filter_status))
+- if lines and not result:
+- message.warning('no output from filter: %s' % filter_cmd)
+- return result
+-
+-def system(name, args, is_macro=False):
+- """
+- Evaluate a system attribute ({name:args}) or system block macro
+- (name::[args]). If is_macro is True then we are processing a system
+- block macro otherwise it's a system attribute.
+- NOTE: The include1 attribute is used internally by the include1::[] macro
+- and is not for public use.
+- """
+- if is_macro:
+- syntax = '%s::[%s]'
+- separator = '\n'
+- else:
+- syntax = '{%s:%s}'
+- separator = writer.newline
+- if name not in ('eval','sys','sys2','include','include1'):
+- if is_macro:
+- msg = 'illegal system macro name: %s' % name
+- else:
+- msg = 'illegal system attribute name: %s' % name
+- message.warning(msg)
+- return None
+- if is_macro:
+- s = subs_attrs(args)
+- if s is None:
+- message.warning('skipped %s: undefined attribute in: %s' % (name,args))
+- return None
+- args = s
+- if name != 'include1':
+- message.verbose(('evaluating: '+syntax) % (name,args))
+- if safe() and name not in ('include','include1'):
+- message.unsafe(syntax % (name,args))
+- return None
+- result = None
+- if name == 'eval':
+- try:
+- result = eval(args)
+- if result is True:
+- result = ''
+- elif result is False:
+- result = None
+- elif result is not None:
+- result = str(result)
+- except Exception:
+- message.warning((syntax+': expression evaluation error') % (name,args))
+- elif name in ('sys','sys2'):
+- result = ''
+- fd,tmp = tempfile.mkstemp()
+- os.close(fd)
+- try:
+- cmd = args
+- cmd = cmd + (' > %s' % tmp)
+- if name == 'sys2':
+- cmd = cmd + ' 2>&1'
+- if os.system(cmd):
+- message.warning((syntax+': non-zero exit status') % (name,args))
+- try:
+- if os.path.isfile(tmp):
+- lines = [s.rstrip() for s in open(tmp)]
+- else:
+- lines = []
+- except Exception:
+- raise EAsciiDoc,(syntax+': temp file read error') % (name,args)
+- result = separator.join(lines)
+- finally:
+- if os.path.isfile(tmp):
+- os.remove(tmp)
+- elif name == 'include':
+- if not os.path.exists(args):
+- message.warning((syntax+': file does not exist') % (name,args))
+- elif not is_safe_file(args):
+- message.unsafe(syntax % (name,args))
+- else:
+- result = [s.rstrip() for s in open(args)]
+- if result:
+- result = subs_attrs(result)
+- result = separator.join(result)
+- result = result.expandtabs(reader.tabsize)
+- else:
+- result = ''
+- elif name == 'include1':
+- result = separator.join(config.include1[args])
+- else:
+- assert False
+- return result
+-
+-def subs_attrs(lines, dictionary=None):
+- """Substitute 'lines' of text with attributes from the global
+- document.attributes dictionary and from 'dictionary' ('dictionary'
+- entries take precedence). Return a tuple of the substituted lines. 'lines'
+- containing undefined attributes are deleted. If 'lines' is a string then
+- return a string.
+-
+- - Attribute references are substituted in the following order: simple,
+- conditional, system.
+- - Attribute references inside 'dictionary' entry values are substituted.
+- """
+-
+- def end_brace(text,start):
+- """Return index following end brace that matches brace at start in
+- text."""
+- assert text[start] == '{'
+- n = 0
+- result = start
+- for c in text[start:]:
+- # Skip braces that are followed by a backslash.
+- if result == len(text)-1 or text[result+1] != '\\':
+- if c == '{': n = n + 1
+- elif c == '}': n = n - 1
+- result = result + 1
+- if n == 0: break
+- return result
+-
+- if type(lines) == str:
+- string_result = True
+- lines = [lines]
+- else:
+- string_result = False
+- lines = list(lines)
+- if dictionary is None:
+- attrs = document.attributes
+- else:
+- # Remove numbered document attributes so they don't clash with
+- # attribute list positional attributes.
+- attrs = {}
+- for k,v in document.attributes.items():
+- if not re.match(r'^\d+$', k):
+- attrs[k] = v
+- # Substitute attribute references inside dictionary values.
+- dictionary = dictionary.copy()
+- for k,v in dictionary.items():
+- if v is None:
+- del dictionary[k]
+- else:
+- v = subs_attrs(str(v))
+- if v is None:
+- del dictionary[k]
+- else:
+- dictionary[k] = v
+- attrs.update(dictionary)
+- # Substitute all attributes in all lines.
+- for i in range(len(lines)-1,-1,-1): # Reverse iterate lines.
+- text = lines[i]
+- # Make it easier for regular expressions.
+- text = text.replace('\\{','{\\')
+- text = text.replace('\\}','}\\')
+- # Expand simple attributes ({name}).
+- # Nested attributes not allowed.
+- reo = re.compile(r'(?su)\{(?P[^\\\W][-\w]*?)\}(?!\\)')
+- pos = 0
+- while True:
+- mo = reo.search(text,pos)
+- if not mo: break
+- s = attrs.get(mo.group('name'))
+- if s is None:
+- pos = mo.end()
+- else:
+- s = str(s)
+- text = text[:mo.start()] + s + text[mo.end():]
+- pos = mo.start() + len(s)
+- # Expand conditional attributes.
+- reo = re.compile(r'(?su)\{(?P[^\\\W][-\w]*?)' \
+- r'(?P\=|\?|!|#|%|@|\$)' \
+- r'(?P.*?)\}(?!\\)')
+- pos = 0
+- while True:
+- mo = reo.search(text,pos)
+- if not mo: break
+- attr = mo.group()
+- name = mo.group('name')
+- lval = attrs.get(name)
+- op = mo.group('op')
+- # mo.end() is not good enough because '{x={y}}' matches '{x={y}'.
+- end = end_brace(text,mo.start())
+- rval = text[mo.start('value'):end-1]
+- if lval is None:
+- if op == '=': s = rval
+- elif op == '?': s = ''
+- elif op == '!': s = rval
+- elif op == '#': s = '{'+name+'}' # So the line is dropped.
+- elif op == '%': s = rval
+- elif op in ('@','$'):
+- s = '{'+name+'}' # So the line is dropped.
+- else:
+- assert False, 'illegal attribute: %s' % attr
+- else:
+- if op == '=': s = lval
+- elif op == '?': s = rval
+- elif op == '!': s = ''
+- elif op == '#': s = rval
+- elif op == '%': s = '{zzzzz}' # So the line is dropped.
+- elif op in ('@','$'):
+- v = re.split(r'(?@:[:]}
+- else:
+- if len(v) == 3: # {@::}
+- s = v[2]
+- else: # {@:}
+- s = ''
+- else:
+- if re_mo:
+- if len(v) == 2: # {$:}
+- s = v[1]
+- elif v[1] == '': # {$::}
+- s = '{zzzzz}' # So the line is dropped.
+- else: # {$::}
+- s = v[1]
+- else:
+- if len(v) == 2: # {$:}
+- s = '{zzzzz}' # So the line is dropped.
+- else: # {$::}
+- s = v[2]
+- else:
+- assert False, 'illegal attribute: %s' % attr
+- s = str(s)
+- text = text[:mo.start()] + s + text[end:]
+- pos = mo.start() + len(s)
+- # Drop line if it contains unsubstituted {name} references.
+- skipped = re.search(r'(?su)\{[^\\\W][-\w]*?\}(?!\\)', text)
+- if skipped:
+- del lines[i]
+- continue;
+- # Expand system attributes.
+- reo = re.compile(r'(?su)\{(?P[^\\\W][-\w]*?):(?P.*?)\}(?!\\)')
+- skipped = False
+- pos = 0
+- while True:
+- mo = reo.search(text,pos)
+- if not mo: break
+- expr = mo.group('expr')
+- expr = expr.replace('{\\','{')
+- expr = expr.replace('}\\','}')
+- s = system(mo.group('action'),expr)
+- if s is None:
+- skipped = True
+- break
+- text = text[:mo.start()] + s + text[mo.end():]
+- pos = mo.start() + len(s)
+- # Drop line if the action returns None.
+- if skipped:
+- del lines[i]
+- continue;
+- # Remove backslash from escaped entries.
+- text = text.replace('{\\','{')
+- text = text.replace('}\\','}')
+- lines[i] = text
+- if string_result:
+- if lines:
+- return '\n'.join(lines)
+- else:
+- return None
+- else:
+- return tuple(lines)
+-
+-def char_encoding():
+- encoding = document.attributes.get('encoding')
+- if encoding:
+- try:
+- codecs.lookup(encoding)
+- except LookupError,e:
+- raise EAsciiDoc,str(e)
+- return encoding
+-
+-def char_len(s):
+- return len(char_decode(s))
+-
+-def char_decode(s):
+- if char_encoding():
+- try:
+- return s.decode(char_encoding())
+- except Exception:
+- raise EAsciiDoc, \
+- "'%s' codec can't decode \"%s\"" % (char_encoding(), s)
+- else:
+- return s
+-
+-def char_encode(s):
+- if char_encoding():
+- return s.encode(char_encoding())
+- else:
+- return s
+-
+-def time_str(t):
+- """Convert seconds since the Epoch to formatted local time string."""
+- t = time.localtime(t)
+- s = time.strftime('%H:%M:%S',t)
+- if time.daylight:
+- result = s + ' ' + time.tzname[1]
+- else:
+- result = s + ' ' + time.tzname[0]
+- # Attempt to convert the localtime to the output encoding.
+- try:
+- result = char_encode(result.decode(locale.getdefaultlocale()[1]))
+- except Exception:
+- pass
+- return result
+-
+-def date_str(t):
+- """Convert seconds since the Epoch to formatted local date string."""
+- t = time.localtime(t)
+- return time.strftime('%Y-%m-%d',t)
+-
+-
+-class Lex:
+- """Lexical analysis routines. Static methods and attributes only."""
+- prev_element = None
+- prev_cursor = None
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def next():
+- """Returns class of next element on the input (None if EOF). The
+- reader is assumed to be at the first line following a previous element,
+- end of file or line one. Exits with the reader pointing to the first
+- line of the next element or EOF (leading blank lines are skipped)."""
+- reader.skip_blank_lines()
+- if reader.eof(): return None
+- # Optimization: If we've already checked for an element at this
+- # position return the element.
+- if Lex.prev_element and Lex.prev_cursor == reader.cursor:
+- return Lex.prev_element
+- if AttributeEntry.isnext():
+- result = AttributeEntry
+- elif AttributeList.isnext():
+- result = AttributeList
+- elif Title.isnext():
+- result = Title
+- elif macros.isnext():
+- result = macros.current
+- elif lists.isnext():
+- result = lists.current
+- elif blocks.isnext():
+- result = blocks.current
+- elif tables_OLD.isnext():
+- result = tables_OLD.current
+- elif tables.isnext():
+- result = tables.current
+- elif BlockTitle.isnext():
+- result = BlockTitle
+- else:
+- if not paragraphs.isnext():
+- raise EAsciiDoc,'paragraph expected'
+- result = paragraphs.current
+- # Optimization: Cache answer.
+- Lex.prev_cursor = reader.cursor
+- Lex.prev_element = result
+- return result
+-
+- @staticmethod
+- def canonical_subs(options):
+- """Translate composite subs values."""
+- if len(options) == 1:
+- if options[0] == 'none':
+- options = ()
+- elif options[0] == 'normal':
+- options = config.subsnormal
+- elif options[0] == 'verbatim':
+- options = config.subsverbatim
+- return options
+-
+- @staticmethod
+- def subs_1(s,options):
+- """Perform substitution specified in 'options' (in 'options' order) on
+- Does not process 'attributes' substitutions."""
+- if not s:
+- return s
+- result = s
+- options = Lex.canonical_subs(options)
+- for o in options:
+- if o == 'specialcharacters':
+- result = config.subs_specialchars(result)
+- elif o == 'attributes':
+- result = subs_attrs(result)
+- elif o == 'quotes':
+- result = subs_quotes(result)
+- elif o == 'specialwords':
+- result = config.subs_specialwords(result)
+- elif o in ('replacements','replacements2'):
+- result = config.subs_replacements(result,o)
+- elif o == 'macros':
+- result = macros.subs(result)
+- elif o == 'callouts':
+- result = macros.subs(result,callouts=True)
+- else:
+- raise EAsciiDoc,'illegal substitution option: %s' % o
+- trace(o, s, result)
+- if not result:
+- break
+- return result
+-
+- @staticmethod
+- def subs(lines,options):
+- """Perform inline processing specified by 'options' (in 'options'
+- order) on sequence of 'lines'."""
+- if not lines or not options:
+- return lines
+- options = Lex.canonical_subs(options)
+- # Join lines so quoting can span multiple lines.
+- para = '\n'.join(lines)
+- if 'macros' in options:
+- para = macros.extract_passthroughs(para)
+- for o in options:
+- if o == 'attributes':
+- # If we don't substitute attributes line-by-line then a single
+- # undefined attribute will drop the entire paragraph.
+- lines = subs_attrs(para.split('\n'))
+- para = '\n'.join(lines)
+- else:
+- para = Lex.subs_1(para,(o,))
+- if 'macros' in options:
+- para = macros.restore_passthroughs(para)
+- return para.splitlines()
+-
+- @staticmethod
+- def set_margin(lines, margin=0):
+- """Utility routine that sets the left margin to 'margin' space in a
+- block of non-blank lines."""
+- # Calculate width of block margin.
+- lines = list(lines)
+- width = len(lines[0])
+- for s in lines:
+- i = re.search(r'\S',s).start()
+- if i < width: width = i
+- # Strip margin width from all lines.
+- for i in range(len(lines)):
+- lines[i] = ' '*margin + lines[i][width:]
+- return lines
+-
+-#---------------------------------------------------------------------------
+-# Document element classes parse AsciiDoc reader input and write DocBook writer
+-# output.
+-#---------------------------------------------------------------------------
+-class Document:
+- def __init__(self):
+- self.doctype = None # 'article','manpage' or 'book'.
+- self.backend = None # -b option argument.
+- self.infile = None # Source file name.
+- self.outfile = None # Output file name.
+- self.attributes = {}
+- self.level = 0 # 0 => front matter. 1,2,3 => sect1,2,3.
+- self.has_errors = False # Set true if processing errors were flagged.
+- self.has_warnings = False # Set true if warnings were flagged.
+- self.safe = True # Default safe mode.
+- def update_attributes(self):
+- # Set implicit attributes.
+- if self.infile and os.path.exists(self.infile):
+- t = os.path.getmtime(self.infile)
+- elif self.infile == '':
+- t = time.time()
+- else:
+- t = None
+- if t:
+- self.attributes['doctime'] = time_str(t)
+- self.attributes['docdate'] = date_str(t)
+- t = time.time()
+- self.attributes['localtime'] = time_str(t)
+- self.attributes['localdate'] = date_str(t)
+- self.attributes['asciidoc-version'] = VERSION
+- self.attributes['backend'] = document.backend
+- self.attributes['doctype'] = document.doctype
+- self.attributes['backend-'+document.backend] = ''
+- self.attributes['doctype-'+document.doctype] = ''
+- self.attributes[document.backend+'-'+document.doctype] = ''
+- self.attributes['asciidoc-file'] = APP_FILE
+- self.attributes['asciidoc-dir'] = APP_DIR
+- self.attributes['user-dir'] = USER_DIR
+- if self.infile != '':
+- self.attributes['infile'] = self.infile
+- self.attributes['indir'] = os.path.dirname(self.infile)
+- self.attributes['docfile'] = self.infile
+- self.attributes['docdir'] = os.path.dirname(self.infile)
+- self.attributes['docname'] = os.path.splitext(
+- os.path.basename(self.infile))[0]
+- if config.verbose:
+- self.attributes['verbose'] = ''
+- # Update with configuration file attributes.
+- self.attributes.update(config.conf_attrs)
+- # Update with command-line attributes.
+- self.attributes.update(config.cmd_attrs)
+- # Extract miscellaneous configuration section entries from attributes.
+- config.load_miscellaneous(config.conf_attrs)
+- config.load_miscellaneous(config.cmd_attrs)
+- self.attributes['newline'] = config.newline
+- if self.outfile:
+- if self.outfile != '':
+- self.attributes['outfile'] = self.outfile
+- self.attributes['outdir'] = os.path.dirname(self.outfile)
+- self.attributes['docname'] = os.path.splitext(
+- os.path.basename(self.outfile))[0]
+- ext = os.path.splitext(self.outfile)[1][1:]
+- elif config.outfilesuffix:
+- ext = config.outfilesuffix[1:]
+- else:
+- ext = ''
+- if ext:
+- self.attributes['filetype'] = ext
+- self.attributes['filetype-'+ext] = ''
+- def load_lang(self,linenos=False):
+- """
+- Load language configuration file.
+- """
+- lang = self.attributes.get('lang')
+- message.linenos = linenos
+- if lang:
+- if not config.load_lang(lang):
+- message.error('missing language conf file: lang-%s.conf' % lang)
+- self.attributes['lang'] = lang # Reinstate new lang attribute.
+- else:
+- message.error('language attribute (lang) is not defined')
+- message.linenos = None # Restore default line number behavior.
+- def set_deprecated_attribute(self,old,new):
+- """
+- Ensures the 'old' name of an attribute that was renamed to 'new' is
+- still honored.
+- """
+- if self.attributes.get(new) is None:
+- if self.attributes.get(old) is not None:
+- self.attributes[new] = self.attributes[old]
+- else:
+- self.attributes[old] = self.attributes[new]
+- def translate(self):
+- assert self.doctype in ('article','manpage','book'), \
+- 'illegal document type'
+- assert self.level == 0
+- config.expand_all_templates()
+- self.load_lang()
+- # Skip leading comments and attribute entries.
+- finished = False
+- attr_count = 0
+- while not finished:
+- finished = True
+- if blocks.isnext() and 'skip' in blocks.current.options:
+- finished = False
+- blocks.current.translate()
+- if macros.isnext() and macros.current.name == 'comment':
+- finished = False
+- macros.current.translate()
+- if AttributeEntry.isnext():
+- finished = False
+- AttributeEntry.translate()
+- if AttributeEntry.name == 'lang':
+- self.load_lang(linenos=True)
+- if attr_count > 0:
+- message.error('lang attribute should be first entry')
+- attr_count += 1
+- message.verbose('writing: '+writer.fname,False)
+- # Process document header.
+- has_header = Lex.next() is Title and Title.level == 0
+- if self.doctype == 'manpage' and not has_header:
+- message.error('manpage document title is mandatory')
+- if has_header:
+- Header.translate()
+- # Command-line entries override header derived entries.
+- self.attributes.update(config.cmd_attrs)
+- # DEPRECATED: revision renamed to revnumber.
+- self.set_deprecated_attribute('revision','revnumber')
+- # DEPRECATED: date renamed to revdate.
+- self.set_deprecated_attribute('date','revdate')
+- if config.header_footer:
+- hdr = config.subs_section('header',{})
+- writer.write(hdr,trace='header')
+- if self.doctype in ('article','book'):
+- # Translate 'preamble' (untitled elements between header
+- # and first section title).
+- if Lex.next() is not Title:
+- stag,etag = config.section2tags('preamble')
+- writer.write(stag,trace='preamble open')
+- Section.translate_body()
+- writer.write(etag,trace='preamble close')
+- else:
+- document.process_author_names()
+- if config.header_footer:
+- hdr = config.subs_section('header',{})
+- writer.write(hdr,trace='header')
+- if Lex.next() is not Title:
+- Section.translate_body()
+- # Process remaining sections.
+- while not reader.eof():
+- if Lex.next() is not Title:
+- raise EAsciiDoc,'section title expected'
+- Section.translate()
+- Section.setlevel(0) # Write remaining unwritten section close tags.
+- # Substitute document parameters and write document footer.
+- if config.header_footer:
+- ftr = config.subs_section('footer',{})
+- writer.write(ftr,trace='footer')
+- def parse_author(self,s):
+- """ Return False if the author is malformed."""
+- attrs = self.attributes # Alias for readability.
+- s = s.strip()
+- mo = re.match(r'^(?P[^<>\s]+)'
+- '(\s+(?P[^<>\s]+))?'
+- '(\s+(?P[^<>\s]+))?'
+- '(\s+<(?P\S+)>)?$',s)
+- if not mo:
+- message.error('malformed author: %s' % s)
+- return False
+- firstname = mo.group('name1')
+- if mo.group('name3'):
+- middlename = mo.group('name2')
+- lastname = mo.group('name3')
+- else:
+- middlename = None
+- lastname = mo.group('name2')
+- firstname = firstname.replace('_',' ')
+- if middlename:
+- middlename = middlename.replace('_',' ')
+- if lastname:
+- lastname = lastname.replace('_',' ')
+- email = mo.group('email')
+- if firstname:
+- attrs['firstname'] = firstname
+- if middlename:
+- attrs['middlename'] = middlename
+- if lastname:
+- attrs['lastname'] = lastname
+- if email:
+- attrs['email'] = email
+- return True
+- def process_author_names(self):
+- """ Calculate any missing author related attributes."""
+- attrs = self.attributes # Alias for readability.
+- firstname = attrs.get('firstname','')
+- middlename = attrs.get('middlename','')
+- lastname = attrs.get('lastname','')
+- author = attrs.get('author')
+- initials = attrs.get('authorinitials')
+- if author and not (firstname or middlename or lastname):
+- if not self.parse_author(author):
+- return
+- attrs['author'] = author.replace('_',' ')
+- self.process_author_names()
+- return
+- if not author:
+- author = '%s %s %s' % (firstname, middlename, lastname)
+- author = author.strip()
+- author = re.sub(r'\s+',' ', author)
+- if not initials:
+- initials = firstname[:1] + middlename[:1] + lastname[:1]
+- initials = initials.upper()
+- names = [firstname,middlename,lastname,author,initials]
+- for i,v in enumerate(names):
+- v = config.subs_specialchars(v)
+- v = subs_attrs(v)
+- names[i] = v
+- firstname,middlename,lastname,author,initials = names
+- if firstname:
+- attrs['firstname'] = firstname
+- if middlename:
+- attrs['middlename'] = middlename
+- if lastname:
+- attrs['lastname'] = lastname
+- if author:
+- attrs['author'] = author
+- if initials:
+- attrs['authorinitials'] = initials
+- if author:
+- attrs['authored'] = ''
+-
+-
+-class Header:
+- """Static methods and attributes only."""
+- REV_LINE_RE = r'^(\D*(?P.*?),)?(?P.*?)(:\s*(?P.*))?$'
+- RCS_ID_RE = r'^\$Id: \S+ (?P\S+) (?P\S+) \S+ (?P\S+) (\S+ )?\$$'
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def translate():
+- assert Lex.next() is Title and Title.level == 0
+- Title.translate()
+- attrs = document.attributes # Alias for readability.
+- attrs['doctitle'] = Title.attributes['title']
+- if document.doctype == 'manpage':
+- # manpage title formatted like mantitle(manvolnum).
+- mo = re.match(r'^(?P.*)\((?P.*)\)$',
+- attrs['doctitle'])
+- if not mo:
+- message.error('malformed manpage title')
+- else:
+- mantitle = mo.group('mantitle').strip()
+- # mantitle is lowered only if in ALL CAPS
+- if mantitle == mantitle.upper():
+- mantitle = mantitle.lower()
+- attrs['mantitle'] = mantitle;
+- attrs['manvolnum'] = mo.group('manvolnum').strip()
+- AttributeEntry.translate_all()
+- s = reader.read_next()
+- mo = None
+- if s:
+- s = reader.read()
+- mo = re.match(Header.RCS_ID_RE,s)
+- if not mo:
+- document.parse_author(s)
+- AttributeEntry.translate_all()
+- if reader.read_next():
+- # Parse revision line.
+- s = reader.read()
+- s = subs_attrs(s)
+- if s:
+- mo = re.match(Header.RCS_ID_RE,s)
+- if not mo:
+- mo = re.match(Header.REV_LINE_RE,s)
+- AttributeEntry.translate_all()
+- s = attrs.get('revnumber')
+- if s:
+- mo = re.match(Header.RCS_ID_RE,s)
+- if mo:
+- revnumber = mo.group('revnumber')
+- if revnumber:
+- attrs['revnumber'] = revnumber.strip()
+- author = mo.groupdict().get('author')
+- if author and 'firstname' not in attrs:
+- document.parse_author(author)
+- revremark = mo.groupdict().get('revremark')
+- if revremark is not None:
+- revremark = [revremark]
+- # Revision remarks can continue on following lines.
+- while reader.read_next() and not AttributeEntry.isnext():
+- revremark.append(reader.read())
+- revremark = Lex.subs(revremark,['normal'])
+- revremark = '\n'.join(revremark).strip()
+- attrs['revremark'] = revremark
+- AttributeEntry.translate_all()
+- revdate = mo.group('revdate')
+- if revdate:
+- attrs['revdate'] = revdate.strip()
+- elif revnumber or revremark:
+- # Set revision date to ensure valid DocBook revision.
+- attrs['revdate'] = attrs['docdate']
+- if document.doctype == 'manpage':
+- # Translate mandatory NAME section.
+- if Lex.next() is not Title:
+- message.error('name section expected')
+- else:
+- Title.translate()
+- if Title.level != 1:
+- message.error('name section title must be at level 1')
+- if not isinstance(Lex.next(),Paragraph):
+- message.error('malformed name section body')
+- lines = reader.read_until(r'^$')
+- s = ' '.join(lines)
+- mo = re.match(r'^(?P.*?)\s+-\s+(?P.*)$',s)
+- if not mo:
+- message.error('malformed name section body')
+- attrs['manname'] = mo.group('manname').strip()
+- attrs['manpurpose'] = mo.group('manpurpose').strip()
+- names = [s.strip() for s in attrs['manname'].split(',')]
+- if len(names) > 9:
+- message.warning('to many manpage names')
+- for i,name in enumerate(names):
+- attrs['manname%d' % (i+1)] = name
+- document.process_author_names()
+-
+-class AttributeEntry:
+- """Static methods and attributes only."""
+- pattern = None
+- subs = None
+- name = None
+- name2 = None
+- value = None
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def isnext():
+- result = False # Assume not next.
+- if not AttributeEntry.pattern:
+- pat = document.attributes.get('attributeentry-pattern')
+- if not pat:
+- message.error("[attributes] missing 'attributeentry-pattern' entry")
+- AttributeEntry.pattern = pat
+- if not AttributeEntry.subs:
+- subs = document.attributes.get('attributeentry-subs')
+- if subs:
+- subs = parse_options(subs,SUBS_OPTIONS,
+- 'illegal [%s] %s: %s' % ('attributes','attributeentry-subs',subs))
+- else:
+- subs = ('specialcharacters','attributes')
+- AttributeEntry.subs = subs
+- line = reader.read_next()
+- if line:
+- # Attribute entry formatted like :[.]:[ ]
+- mo = re.match(AttributeEntry.pattern,line)
+- if mo:
+- AttributeEntry.name = mo.group('attrname')
+- AttributeEntry.name2 = mo.group('attrname2')
+- AttributeEntry.value = mo.group('attrvalue') or ''
+- AttributeEntry.value = AttributeEntry.value.strip()
+- result = True
+- return result
+- @staticmethod
+- def translate():
+- assert Lex.next() is AttributeEntry
+- attr = AttributeEntry # Alias for brevity.
+- reader.read() # Discard attribute entry from reader.
+- if AttributeEntry.name2: # The entry is a conf file entry.
+- section = {}
+- # Some sections can have name! syntax.
+- if attr.name in ('attributes','miscellaneous') and attr.name2[-1] == '!':
+- section[attr.name] = [attr.name2]
+- else:
+- section[attr.name] = ['%s=%s' % (attr.name2,attr.value)]
+- config.load_sections(section)
+- config.validate()
+- else: # The entry is an attribute.
+- if attr.name[-1] == '!':
+- # Names like name! undefine the attribute.
+- attr.name = attr.name[:-1]
+- attr.value = None
+- # Strip white space and illegal name chars.
+- attr.name = re.sub(r'(?u)[^\w\-_]', '', attr.name).lower()
+- # Don't override command-line attributes (the exception is the
+- # system 'trace' attribute).
+- if attr.name in config.cmd_attrs and attr.name != 'trace':
+- return
+- # Update document.attributes from previously parsed attribute.
+- if attr.name == 'attributeentry-subs':
+- AttributeEntry.subs = None # Force update in isnext().
+- elif attr.value:
+- attr.value = Lex.subs((attr.value,), attr.subs)
+- attr.value = writer.newline.join(attr.value)
+- if attr.value is not None:
+- document.attributes[attr.name] = attr.value
+- elif attr.name in document.attributes:
+- del document.attributes[attr.name]
+- @staticmethod
+- def translate_all():
+- """ Process all contiguous attribute lines on reader."""
+- while AttributeEntry.isnext():
+- AttributeEntry.translate()
+-
+-class AttributeList:
+- """Static methods and attributes only."""
+- pattern = None
+- match = None
+- attrs = {}
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def initialize():
+- if not 'attributelist-pattern' in document.attributes:
+- message.error("[attributes] missing 'attributelist-pattern' entry")
+- AttributeList.pattern = document.attributes['attributelist-pattern']
+- @staticmethod
+- def isnext():
+- result = False # Assume not next.
+- line = reader.read_next()
+- if line:
+- mo = re.match(AttributeList.pattern, line)
+- if mo:
+- AttributeList.match = mo
+- result = True
+- return result
+- @staticmethod
+- def translate():
+- assert Lex.next() is AttributeList
+- reader.read() # Discard attribute list from reader.
+- attrlist = {}
+- d = AttributeList.match.groupdict()
+- for k,v in d.items():
+- if v is not None:
+- if k == 'attrlist':
+- v = subs_attrs(v)
+- if v:
+- parse_attributes(v, attrlist)
+- else:
+- AttributeList.attrs[k] = v
+- # Substitute single quoted attribute values.
+- reo = re.compile(r"^'.*'$")
+- for k,v in attrlist.items():
+- if reo.match(str(v)):
+- attrlist[k] = Lex.subs_1(v[1:-1],SUBS_NORMAL)
+- AttributeList.attrs.update(attrlist)
+- @staticmethod
+- def style():
+- return AttributeList.attrs.get('style') or AttributeList.attrs.get('1')
+- @staticmethod
+- def consume(d):
+- """Add attribute list to the dictionary 'd' and reset the
+- list."""
+- if AttributeList.attrs:
+- d.update(AttributeList.attrs)
+- AttributeList.attrs = {}
+- # Generate option attributes.
+- if 'options' in d:
+- options = parse_options(d['options'], (), 'illegal option name')
+- for option in options:
+- d[option+'-option'] = ''
+-
+-class BlockTitle:
+- """Static methods and attributes only."""
+- title = None
+- pattern = None
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def isnext():
+- result = False # Assume not next.
+- line = reader.read_next()
+- if line:
+- mo = re.match(BlockTitle.pattern,line)
+- if mo:
+- BlockTitle.title = mo.group('title')
+- result = True
+- return result
+- @staticmethod
+- def translate():
+- assert Lex.next() is BlockTitle
+- reader.read() # Discard title from reader.
+- # Perform title substitutions.
+- if not Title.subs:
+- Title.subs = config.subsnormal
+- s = Lex.subs((BlockTitle.title,), Title.subs)
+- s = writer.newline.join(s)
+- if not s:
+- message.warning('blank block title')
+- BlockTitle.title = s
+- @staticmethod
+- def consume(d):
+- """If there is a title add it to dictionary 'd' then reset title."""
+- if BlockTitle.title:
+- d['title'] = BlockTitle.title
+- BlockTitle.title = None
+-
+-class Title:
+- """Processes Header and Section titles. Static methods and attributes
+- only."""
+- # Class variables
+- underlines = ('==','--','~~','^^','++') # Levels 0,1,2,3,4.
+- subs = ()
+- pattern = None
+- level = 0
+- attributes = {}
+- sectname = None
+- section_numbers = [0]*len(underlines)
+- dump_dict = {}
+- linecount = None # Number of lines in title (1 or 2).
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def translate():
+- """Parse the Title.attributes and Title.level from the reader. The
+- real work has already been done by parse()."""
+- assert Lex.next() is Title
+- # Discard title from reader.
+- for i in range(Title.linecount):
+- reader.read()
+- Title.setsectname()
+- # Perform title substitutions.
+- if not Title.subs:
+- Title.subs = config.subsnormal
+- s = Lex.subs((Title.attributes['title'],), Title.subs)
+- s = writer.newline.join(s)
+- if not s:
+- message.warning('blank section title')
+- Title.attributes['title'] = s
+- @staticmethod
+- def isnext():
+- lines = reader.read_ahead(2)
+- return Title.parse(lines)
+- @staticmethod
+- def parse(lines):
+- """Parse title at start of lines tuple."""
+- if len(lines) == 0: return False
+- if len(lines[0]) == 0: return False # Title can't be blank.
+- # Check for single-line titles.
+- result = False
+- for level in range(len(Title.underlines)):
+- k = 'sect%s' % level
+- if k in Title.dump_dict:
+- mo = re.match(Title.dump_dict[k], lines[0])
+- if mo:
+- Title.attributes = mo.groupdict()
+- Title.level = level
+- Title.linecount = 1
+- result = True
+- break
+- if not result:
+- # Check for double-line titles.
+- if not Title.pattern: return False # Single-line titles only.
+- if len(lines) < 2: return False
+- title,ul = lines[:2]
+- title_len = char_len(title)
+- ul_len = char_len(ul)
+- if ul_len < 2: return False
+- # Fast elimination check.
+- if ul[:2] not in Title.underlines: return False
+- # Length of underline must be within +-3 of title.
+- if not (ul_len-3 < title_len < ul_len+3): return False
+- # Check for valid repetition of underline character pairs.
+- s = ul[:2]*((ul_len+1)/2)
+- if ul != s[:ul_len]: return False
+- # Don't be fooled by back-to-back delimited blocks, require at
+- # least one alphanumeric character in title.
+- if not re.search(r'(?u)\w',title): return False
+- mo = re.match(Title.pattern, title)
+- if mo:
+- Title.attributes = mo.groupdict()
+- Title.level = list(Title.underlines).index(ul[:2])
+- Title.linecount = 2
+- result = True
+- # Check for expected pattern match groups.
+- if result:
+- if not 'title' in Title.attributes:
+- message.warning('[titles] entry has no group')
+- Title.attributes['title'] = lines[0]
+- for k,v in Title.attributes.items():
+- if v is None: del Title.attributes[k]
+- Title.attributes['level'] = str(Title.level)
+- return result
+- @staticmethod
+- def load(entries):
+- """Load and validate [titles] section entries dictionary."""
+- if 'underlines' in entries:
+- errmsg = 'malformed [titles] underlines entry'
+- try:
+- underlines = parse_list(entries['underlines'])
+- except Exception:
+- raise EAsciiDoc,errmsg
+- if len(underlines) != len(Title.underlines):
+- raise EAsciiDoc,errmsg
+- for s in underlines:
+- if len(s) !=2:
+- raise EAsciiDoc,errmsg
+- Title.underlines = tuple(underlines)
+- Title.dump_dict['underlines'] = entries['underlines']
+- if 'subs' in entries:
+- Title.subs = parse_options(entries['subs'], SUBS_OPTIONS,
+- 'illegal [titles] subs entry')
+- Title.dump_dict['subs'] = entries['subs']
+- if 'sectiontitle' in entries:
+- pat = entries['sectiontitle']
+- if not pat or not is_re(pat):
+- raise EAsciiDoc,'malformed [titles] sectiontitle entry'
+- Title.pattern = pat
+- Title.dump_dict['sectiontitle'] = pat
+- if 'blocktitle' in entries:
+- pat = entries['blocktitle']
+- if not pat or not is_re(pat):
+- raise EAsciiDoc,'malformed [titles] blocktitle entry'
+- BlockTitle.pattern = pat
+- Title.dump_dict['blocktitle'] = pat
+- # Load single-line title patterns.
+- for k in ('sect0','sect1','sect2','sect3','sect4'):
+- if k in entries:
+- pat = entries[k]
+- if not pat or not is_re(pat):
+- raise EAsciiDoc,'malformed [titles] %s entry' % k
+- Title.dump_dict[k] = pat
+- # TODO: Check we have either a Title.pattern or at least one
+- # single-line title pattern -- can this be done here or do we need
+- # check routine like the other block checkers?
+- @staticmethod
+- def dump():
+- dump_section('titles',Title.dump_dict)
+- @staticmethod
+- def setsectname():
+- """
+- Set Title section name:
+- If the first positional or 'template' attribute is set use it,
+- next search for section title in [specialsections],
+- if not found use default 'sect' name.
+- """
+- sectname = AttributeList.attrs.get('1')
+- if sectname and sectname != 'float':
+- Title.sectname = sectname
+- elif 'template' in AttributeList.attrs:
+- Title.sectname = AttributeList.attrs['template']
+- else:
+- for pat,sect in config.specialsections.items():
+- mo = re.match(pat,Title.attributes['title'])
+- if mo:
+- title = mo.groupdict().get('title')
+- if title is not None:
+- Title.attributes['title'] = title.strip()
+- else:
+- Title.attributes['title'] = mo.group().strip()
+- Title.sectname = sect
+- break
+- else:
+- Title.sectname = 'sect%d' % Title.level
+- @staticmethod
+- def getnumber(level):
+- """Return next section number at section 'level' formatted like
+- 1.2.3.4."""
+- number = ''
+- for l in range(len(Title.section_numbers)):
+- n = Title.section_numbers[l]
+- if l == 0:
+- continue
+- elif l < level:
+- number = '%s%d.' % (number, n)
+- elif l == level:
+- number = '%s%d.' % (number, n + 1)
+- Title.section_numbers[l] = n + 1
+- elif l > level:
+- # Reset unprocessed section levels.
+- Title.section_numbers[l] = 0
+- return number
+-
+-
+-class Section:
+- """Static methods and attributes only."""
+- endtags = [] # Stack of currently open section (level,endtag) tuples.
+- ids = [] # List of already used ids.
+- def __init__(self):
+- raise AssertionError,'no class instances allowed'
+- @staticmethod
+- def savetag(level,etag):
+- """Save section end."""
+- Section.endtags.append((level,etag))
+- @staticmethod
+- def setlevel(level):
+- """Set document level and write open section close tags up to level."""
+- while Section.endtags and Section.endtags[-1][0] >= level:
+- writer.write(Section.endtags.pop()[1],trace='section close')
+- document.level = level
+- @staticmethod
+- def gen_id(title):
+- """
+- The normalized value of the id attribute is an NCName according to
+- the 'Namespaces in XML' Recommendation:
+- NCName ::= NCNameStartChar NCNameChar*
+- NCNameChar ::= NameChar - ':'
+- NCNameStartChar ::= Letter | '_'
+- NameChar ::= Letter | Digit | '.' | '-' | '_' | ':'
+- """
+- base_ident = re.sub(r'[^a-zA-Z0-9]+', '_', title).strip('_').lower()
+- # Prefix the ID name with idprefix attribute or underscore if not
+- # defined. Prefix ensures the ID does not clash with existing IDs.
+- idprefix = document.attributes.get('idprefix','_')
+- base_ident = idprefix + base_ident
+- i = 1
+- while True:
+- if i == 1:
+- ident = base_ident
+- else:
+- ident = '%s_%d' % (base_ident, i)
+- if ident not in Section.ids:
+- Section.ids.append(ident)
+- return ident
+- else:
+- ident = base_ident
+- i += 1
+- @staticmethod
+- def set_id():
+- if not document.attributes.get('sectids') is None \
+- and 'id' not in AttributeList.attrs:
+- # Generate ids for sections.
+- AttributeList.attrs['id'] = Section.gen_id(Title.attributes['title'])
+- @staticmethod
+- def translate():
+- assert Lex.next() is Title
+- prev_sectname = Title.sectname
+- Title.translate()
+- if Title.level == 0 and document.doctype != 'book':
+- message.error('only book doctypes can contain level 0 sections')
+- if Title.level > document.level \
+- and document.backend == 'docbook' \
+- and prev_sectname in ('colophon','abstract', \
+- 'dedication','glossary','bibliography'):
+- message.error('%s section cannot contain sub-sections' % prev_sectname)
+- if Title.level > document.level+1:
+- # Sub-sections of multi-part book level zero Preface and Appendices
+- # are meant to be out of sequence.
+- if document.doctype == 'book' \
+- and document.level == 0 \
+- and Title.level == 2 \
+- and prev_sectname in ('preface','appendix'):
+- pass
+- else:
+- message.warning('section title out of sequence: '
+- 'expected level %d, got level %d'
+- % (document.level+1, Title.level))
+- Section.set_id()
+- Section.setlevel(Title.level)
+- Title.attributes['sectnum'] = Title.getnumber(document.level)
+- AttributeList.consume(Title.attributes)
+- stag,etag = config.section2tags(Title.sectname,Title.attributes)
+- Section.savetag(Title.level,etag)
+- writer.write(stag,trace='section open')
+- Section.translate_body()
+- @staticmethod
+- def translate_body(terminator=Title):
+- isempty = True
+- next = Lex.next()
+- while next and next is not terminator:
+- if (isinstance(terminator,DelimitedBlock)
+- and next is Title and AttributeList.style() != 'float'):
+- message.error('section title not permitted in delimited block')
+- next.translate()
+- next = Lex.next()
+- if next is Title and AttributeList.style() == 'float':
+- # Process floating titles.
+- template = 'floatingtitle'
+- if template in config.sections:
+- Title.translate()
+- Section.set_id()
+- AttributeList.consume(Title.attributes)
+- stag,etag = config.section2tags(template,Title.attributes)
+- writer.write(stag,trace='floating title')
+- next = Lex.next()
+- else:
+- message.warning('missing template section: [%s]' % template)
+- isempty = False
+- # The section is not empty if contains a subsection.
+- if next and isempty and Title.level > document.level:
+- isempty = False
+- # Report empty sections if invalid markup will result.
+- if isempty:
+- if document.backend == 'docbook' and Title.sectname != 'index':
+- message.error('empty section is not valid')
+-
+-class AbstractBlock:
+- def __init__(self):
+- # Configuration parameter names common to all blocks.
+- self.CONF_ENTRIES = ('delimiter','options','subs','presubs','postsubs',
+- 'posattrs','style','.*-style','template','filter')
+- self.start = None # File reader cursor at start delimiter.
+- self.name=None # Configuration file section name.
+- # Configuration parameters.
+- self.delimiter=None # Regular expression matching block delimiter.
+- self.delimiter_reo=None # Compiled delimiter.
+- self.template=None # template section entry.
+- self.options=() # options entry list.
+- self.presubs=None # presubs/subs entry list.
+- self.postsubs=() # postsubs entry list.
+- self.filter=None # filter entry.
+- self.posattrs=() # posattrs entry list.
+- self.style=None # Default style.
+- self.styles=OrderedDict() # Each entry is a styles dictionary.
+- # Before a block is processed it's attributes (from it's
+- # attributes list) are merged with the block configuration parameters
+- # (by self.merge_attributes()) resulting in the template substitution
+- # dictionary (self.attributes) and the block's processing parameters
+- # (self.parameters).
+- self.attributes={}
+- # The names of block parameters.
+- self.PARAM_NAMES=('template','options','presubs','postsubs','filter')
+- self.parameters=None
+- # Leading delimiter match object.
+- self.mo=None
+- def short_name(self):
+- """ Return the text following the last dash in the section namem """
+- i = self.name.rfind('-')
+- if i == -1:
+- return self.name
+- else:
+- return self.name[i+1:]
+- def error(self, msg, cursor=None, halt=False):
+- message.error('[%s] %s' % (self.name,msg), cursor, halt)
+- def is_conf_entry(self,param):
+- """Return True if param matches an allowed configuration file entry
+- name."""
+- for s in self.CONF_ENTRIES:
+- if re.match('^'+s+'$',param):
+- return True
+- return False
+- def load(self,name,entries):
+- """Update block definition from section 'entries' dictionary."""
+- self.name = name
+- self.update_parameters(entries, self, all=True)
+- def update_parameters(self, src, dst=None, all=False):
+- """
+- Parse processing parameters from src dictionary to dst object.
+- dst defaults to self.parameters.
+- If all is True then copy src entries that aren't parameter names.
+- """
+- dst = dst or self.parameters
+- msg = '[%s] malformed entry %%s: %%s' % self.name
+- def copy(obj,k,v):
+- if isinstance(obj,dict):
+- obj[k] = v
+- else:
+- setattr(obj,k,v)
+- for k,v in src.items():
+- if not re.match(r'\d+',k) and not is_name(k):
+- raise EAsciiDoc, msg % (k,v)
+- if k == 'template':
+- if not is_name(v):
+- raise EAsciiDoc, msg % (k,v)
+- copy(dst,k,v)
+- elif k == 'filter':
+- copy(dst,k,v)
+- elif k == 'options':
+- if isinstance(v,str):
+- v = parse_options(v, (), msg % (k,v))
+- copy(dst,k,v)
+- elif k in ('subs','presubs','postsubs'):
+- # Subs is an alias for presubs.
+- if k == 'subs': k = 'presubs'
+- if isinstance(v,str):
+- v = parse_options(v, SUBS_OPTIONS, msg % (k,v))
+- copy(dst,k,v)
+- elif k == 'delimiter':
+- if v and is_re(v):
+- copy(dst,k,v)
+- else:
+- raise EAsciiDoc, msg % (k,v)
+- elif k == 'style':
+- if is_name(v):
+- copy(dst,k,v)
+- else:
+- raise EAsciiDoc, msg % (k,v)
+- elif k == 'posattrs':
+- v = parse_options(v, (), msg % (k,v))
+- copy(dst,k,v)
+- else:
+- mo = re.match(r'^(?P