ZConfig-3.1.0/0000755000076500000240000000000012610530670013170 5ustar do3ccstaff00000000000000ZConfig-3.1.0/bootstrap.py0000644000076500000240000001401612610530667015567 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) parser.add_option("--allow-site-packages", action="store_true", default=False, help=("Let bootstrap.py use existing site packages")) options, args = parser.parse_args() ###################################################################### # load/install setuptools try: if options.allow_site_packages: import setuptools import pkg_resources from urllib.request import urlopen except ImportError: from urllib2 import urlopen ez = {} exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) if not options.allow_site_packages: # ez_setup imports site, which adds site packages # this will remove them from the path to ensure that incompatible versions # of setuptools are not in the path import site # inside a virtualenv, there is no 'getsitepackages'. # We can't remove these reliably if hasattr(site, 'getsitepackages'): for sitepackage_path in site.getsitepackages(): sys.path[:] = [x for x in sys.path if sitepackage_path not in x] setup_args = dict(to_dir=tmpeggs, download_delay=0) ez['use_setuptools'](**setup_args) import setuptools import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception( "Failed to execute command:\n%s" % repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) ZConfig-3.1.0/buildout.cfg0000644000076500000240000000041512610530667015506 0ustar do3ccstaff00000000000000[buildout] develop = . parts = nosetests test py prefer-final = true [nosetests] recipe = zc.recipe.egg eggs = nose scripts = nosetests [test] recipe = zc.recipe.testrunner eggs = ZConfig defaults = ['-1'] [py] recipe = zc.recipe.egg eggs = ZConfig interpreter = py ZConfig-3.1.0/CHANGES.txt0000644000076500000240000001651212610530667015014 0ustar do3ccstaff00000000000000========================== Change History for ZConfig ========================== 3.1.0 (2015-10-17) ------------------ - Add ability to do variable substitution from environment variables using $() syntax. 3.0.4 (2014-03-20) ------------------ - Added Python 3.4 support. 3.0.3 (2013-03-02) ------------------ - Added Python 3.2 support. 3.0.2 (2013-02-14) ------------------ - Fixed ResourceWarning in BaseLoader.openResource(). 3.0.1 (2013-02-13) ------------------ - Removed an accidentally left `pdb` statement from the code. - Fix a bug in Python 3 with the custom string `repr()` function. 3.0.0 (2013-02-13) ------------------ - Added Python 3.3 support. - Dropped Python 2.4 and 2.5 support. 2.9.3 (2012-06-25) ------------------ - Fixed: port values of 0 weren't allowed. Port 0 is used to request an ephemeral port. 2.9.2 (2012-02-11) ------------------ - Adjust test classes to avoid base classes being considered separate test cases by (at least) the "nose" test runner. 2.9.1 (2012-02-11) ------------------ - Make FileHandler.reopen thread safe. 2.9.0 (2011-03-22) ------------------ - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. 2.8.0 (2010-04-13) ------------------ - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. 2.7.1 (2009-06-13) ------------------ - Improved documentation - Fixed tests failures on windows. 2.7.0 (2009-06-11) ------------------ - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. 2.6.1 (2008-12-05) ------------------ - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. 2.6.0 (2008-09-03) ------------------ - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. 2.5.1 (2007-12-24) ------------------ - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. 2.3.1 (2005-08-21) ------------------ - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. 2.3 (2005-05-18) ---------------- - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. 2.2 (2004-04-21) ---------------- - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. 2.1 (2004-04-12) ---------------- - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. 2.0 (2003-10-27) ---------------- - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. 1.0 (2003-03-25) ---------------- - Initial release. ZConfig-3.1.0/COPYRIGHT.txt0000644000076500000240000000004012610530667015301 0ustar do3ccstaff00000000000000Zope Foundation and ContributorsZConfig-3.1.0/doc/0000755000076500000240000000000012610530670013735 5ustar do3ccstaff00000000000000ZConfig-3.1.0/doc/Makefile0000644000076500000240000000273212610530667015407 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # Rules to convert the documentation to a single PDF file. # # PostScript, HTML, and plain text output are also supported, though # PDF is the default. # # See the README.txt file for information on the mkhowto program used # to generate the formatted versions of the documentation. .PHONY: default all html pdf ps text default: pdf all: html pdf ps text html: zconfig/zconfig.html pdf: zconfig.pdf ps: zconfig.ps text: zconfig.txt zconfig/zconfig.html: zconfig.tex schema.dtd xmlmarkup.perl mkhowto --html $< zconfig.pdf: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --pdf $< zconfig.ps: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --postscript $< zconfig.txt: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --text $< clean: rm -f zconfig.l2h zconfig.l2h~ clobber: clean rm -f zconfig.pdf zconfig.ps zconfig.txt rm -rf zconfig ZConfig-3.1.0/doc/README.txt0000644000076500000240000000121612610530667015441 0ustar do3ccstaff00000000000000The zconfig.tex document in this directory contains the reference documentation for the ZConfig package. This documentation is written using the Python LaTeX styles. To format the documentation, get a copy of the Python documentation tools (the Doc/ directory from the Python sources), and create a symlink to the tools/mkhowto script from some convenient bin/ directory. You will need to have a fairly complete set of documentation tools installed on your platform; see http://www.python.org/doc/current/doc/doc.html for more information on the tools. This documentation requires the latest version of the Python documentation tools from CVS. ZConfig-3.1.0/doc/schema.dtd0000644000076500000240000000655412610530667015712 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/doc/xmlmarkup.perl0000644000076500000240000000350512610530667016652 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # LaTeX2HTML support for the xmlmarkup package. Doesn't do indexing. package main; sub do_cmd_element{ local($_) = @_; my $name = next_argument(); return "$name" . $_; } sub do_cmd_attribute{ local($_) = @_; my $name = next_argument(); return "$name" . $_; } sub do_env_attributedesc{ local($_) = @_; my $name = next_argument(); my $valuetype = next_argument(); return ("\n
" . "\n
$name" . "   ($valuetype)" . "\n
" . $_ . "
"); } sub do_env_elementdesc{ local($_) = @_; my $name = next_argument(); my $contentmodel = next_argument(); return ("\n
" . "\n
<" . "$name>" . "\n
$contentmodel" . "\n
</" . "$name>" . "\n
" . $_ . "
"); } 1; # Must end with this, because Perl is bogus. ZConfig-3.1.0/doc/xmlmarkup.sty0000644000076500000240000000256412610530667016533 0ustar do3ccstaff00000000000000%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Copyright (c) 2003 Zope Foundation and Contributors. % All Rights Reserved. % % This software is subject to the provisions of the Zope Public License, % Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. % THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED % WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED % WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS % FOR A PARTICULAR PURPOSE. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Define some simple markup for the LaTeX command documentation: \ProvidesPackage{xmlmarkup} \RequirePackage{python} % fulllineitems environment \newcommand{\element}[1]{\code{#1}} \newcommand{\attribute}[1]{\code{#1}} % \begin{elementdesc}{type}{content-model} \newenvironment{elementdesc}[2]{ \begin{fulllineitems} \item[\code{\textless{\bfseries #1}\textgreater}] \code{#2} \item[\code{\textless/{\bfseries #1}\textgreater}] \index{#1 element@\py@idxcode{#1} element} \index{elements!#1@\py@idxcode{#1}} }{\end{fulllineitems}} % \begin{attributedesc}{name}{content-type} \newenvironment{attributedesc}[2]{ \begin{fulllineitems} \item[\code{\bfseries#1}{\quad(#2)}] \index{#1@\py@idxcode{#1}} }{\end{fulllineitems}} ZConfig-3.1.0/doc/zconfig.pdf0000644000076500000240000057075612610530667016121 0ustar do3ccstaff00000000000000%PDF-1.4 % 28 0 obj << /Length 2046 /Filter /FlateDecode >> stream xZMs6W( zKd&dNiM6}U긿KbI"H1I{'ݷ H, őg1 Cd(hW%Ƅ}Y2 .~i[ \ n.4 n.("Z&dp >n>Ro ۹8s:ߦv]z3tM?]:!y]YTٕߥ4q N"B@I"dXݺa KN`Hj.+fE bBP&pnaPtdVWNYEU4& $n;!F [؅xe@xŊ]Q~:?tF35+ IcZJ&L2%G^kKGxkB'yb_0Éd6яWwYXn:_lLd]8Yܥv<;ovۗv䐧8mHx۷[M#z2Qpn8æZ;/ժYYQU+|52 ઻ Y;tuś,_[ŊYV0{ȳͭHܥ4;9)8ěZjH2F0!݋ʁG!!{hf@06ulה^n h]:3)C5j9tpŠWL88_&DZ#Tuxlq2]FGϋBVB1j>B>ՕߣCI_ 6U-jA6st=T.[Hsǹ0Ù aB¡`L ;5c`\5te Rbї(PYqhC"s@W^C9);.>FzTh(;0tp*TF:F18#,4.3_@X(ZR]vj(qBND%NNX4zY ҵHu(Ea@GC1%=GQhO hӰ/syAd`E605Z8DKZ`(Uۜ/-v8`#0b`mݡ9%mY<:hmtFqMv ?FL1WЧti.N_׏K6'=<)\vPj*aȕ N G$,o qv\0"%&qInZ!6rs5ѣ+xl8A8Ѧ-sBABq/-=-~eGsmӪ_y`~2v]7k+3uF(v`Tn%ʋxHQLX( SKQy"ꗞqFR[45Iݓ-(&> Gfpz^`Ĵkt?dkШuʺ+!28_=Lvj/mp$$?Ӻwi=T>LҺO[R5DԸk]ЇL@45ljF _[?V>Ou#'tVQؾ_/E lqy[MUI=Ij8EO:,mȶ:2V!C1ܣVx r :I>ݯx ĩ}O_{Gkt\Ji;Fz> endobj 1 0 obj << /Type /Annot /Border [0 0 0] /Rect [455.098 551.76 540 560.237] /Subtype/Link/A<> >> endobj 2 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 441.927 143.501 448.931] /Subtype /Link /A << /S /GoTo /D (page002) >> >> endobj 3 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 418.121 181.05 427.013] /Subtype /Link /A << /S /GoTo /D (page002) >> >> endobj 4 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 406.037 257.354 414.988] /Subtype /Link /A << /S /GoTo /D (page003) >> >> endobj 5 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 396.099 229.398 403.033] /Subtype /Link /A << /S /GoTo /D (page004) >> >> endobj 6 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 372.293 221.169 381.185] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 7 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 362.226 183.461 369.16] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 8 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 348.253 196.751 357.205] /Subtype /Link /A << /S /GoTo /D (page010) >> >> endobj 9 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 336.298 229.797 345.249] /Subtype /Link /A << /S /GoTo /D (page011) >> >> endobj 10 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 314.51 216.238 323.401] /Subtype /Link /A << /S /GoTo /D (page011) >> >> endobj 11 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 292.592 265.503 301.484] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 12 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 281.1 253.32 289.459] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 13 0 obj << /Type /Annot /Border [0 0 0] /Rect [109.858 268.552 220.851 277.503] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 14 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 257.19 259.298 265.548] /Subtype /Link /A << /S /GoTo /D (page015) >> >> endobj 15 0 obj << /Type /Annot /Border [0 0 0] /Rect [109.858 244.642 225.254 253.593] /Subtype /Link /A << /S /GoTo /D (page017) >> >> endobj 16 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 222.854 246.903 231.745] /Subtype /Link /A << /S /GoTo /D (page017) >> >> endobj 17 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 200.936 261.638 209.827] /Subtype /Link /A << /S /GoTo /D (page019) >> >> endobj 18 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 188.851 159.382 197.802] /Subtype /Link /A << /S /GoTo /D (page021) >> >> endobj 19 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 167.063 312.617 175.954] /Subtype /Link /A << /S /GoTo /D (page021) >> >> endobj 20 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 145.145 294.435 154.036] /Subtype /Link /A << /S /GoTo /D (page022) >> >> endobj 21 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 133.06 170.998 142.011] /Subtype /Link /A << /S /GoTo /D (page023) >> >> endobj 22 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 111.272 329.164 120.163] /Subtype /Link /A << /S /GoTo /D (page023) >> >> endobj 23 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 89.354 300.881 98.246] /Subtype /Link /A << /S /GoTo /D (page024) >> >> endobj 24 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 77.269 149.15 86.221] /Subtype /Link /A << /S /GoTo /D (page025) >> >> endobj 26 0 obj << /Font << /F29 29 0 R /F30 30 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F38 34 0 R >> /ProcSet [ /PDF /Text ] >> endobj 54 0 obj << /Length 2302 /Filter /FlateDecode >> stream xڵYm ~b"8_d߶HpA(籧/)R~[wPp#QERC+!,:d" 8;TwaP$0H`.89A!#yx -vd%W>KLnys"<h1ޗ/ s]GQq?bSajʽUDXj`°o!mk\ߙM~q" q!V6.`/D\Iie0O8!$DSiM^Fx۴Ht>A ?uNٕ#&miu3M]myS,zvQٔ,{ =xڝ(BF텆1V-e'G1j5M<; ,ɉ#+0`HepX ^!UZt@ w0H6%IN2 ΞJbZuVnV>$zbJ]L]0Spc3t X7o&`AnKPE!264*ֿWwUBVhfxQsu R%-Tj|l/Eje"@$O |r:&m$| zB.Hȕ /'Z"l[9g!I1 /By_Jg `(bx.?l|(DWb볍YOF+(a>i-llL]`uZAPR3Pך^%1N,N,4-jڤ[Չ*Uc'Y<5qz: 4Xr'JY~EvRnsr|F܋r`]H j݌',m%[`߻JO5^:'m@ѧvvLkl;5[WW|٬ۯ)rk'Z^YAuF5~iLWiCIUomK46[I[u0N ޿AP֒{ci\>*e Mmɜ<;kgË̤̦'"mJ< rW86s-~=cr] ٤>3&{[mU?w7d1pe&s[fwőe~nKһdvaAJ~TDž9#LeE4Nn(Nw; Df҅@n7=+V [I*^xP\b 즞N\lZAga22 dḇy汳l5p~KqXPam7K2VS2ڷ':R U,RM~c:4ƾְ)by]S3V8~a1Q-bs2]3L量g88e,O8oV&+wp{f2SE=0hӊ _B5> }AY=S|f AX ?hDkKq1M?*߿` endstream endobj 53 0 obj << /Type /Page /Contents 54 0 R /Resources 52 0 R /MediaBox [0 0 612 792] /Parent 35 0 R /Annots [ 25 0 R 51 0 R ] >> endobj 25 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 708.01 236.751 716.902] /Subtype /Link /A << /S /GoTo /D (page025) >> >> endobj 51 0 obj << /Type /Annot /Border [0 0 0] /Rect [114.485 400.998 186.216 409.949] /Subtype/Link/A<> >> endobj 36 0 obj << /D [53 0 R /XYZ 72 744.907 null] >> endobj 55 0 obj << /D [53 0 R /XYZ 173.268 641.165 null] >> endobj 57 0 obj << /D [53 0 R /XYZ 231.327 431.09 null] >> endobj 52 0 obj << /Font << /F34 32 0 R /F29 29 0 R /F32 31 0 R /F38 34 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 60 0 obj << /Length 1692 /Filter /FlateDecode >> stream xXK6PH+RbQ`$ZdM1=3RdeCOX| Az}s ˌgãs/gQŹPy)@R</´WEuYg 8>zP DD/ C)qyAq#nP]n_IpZ6nwv cGt+rMB#㨥dg'J:oU 4ǜ{ IDgr_o?nV=}RT *|[OCc^8̯`O}jt1lː *׵>QRzpA`5^\{^ڻ{r >Fiج'!8(Ѹ0$xي*prGtc6Dp>\Pd m26n HЊf aYfJnJC 8g`A^!DlWdi<'KLʸHfZ-̝Fve`ڄ$Rl.51Ko 0]; ͡9Ի:9V`a 5TX6n0qT|J,uΝ-BYy/ kwlcnHƔ;j!⒭]RD.Rm[-q3\tTc8}@!|5.AzV=°k5[\^&y؃B%*:},@xm 6)c vФcX6*1d|<<˓lV'=TI< =#֊<GvxM6`Wwu^YɕڨP ./J"JAƯ>]Vb1xV`$r|AZeQaJX't7WΦՀ0'@ˁ(M‚:ѸT)c}vWxmk8Cv`jX-o\mNBoZ4RvxK{d[KU_-Ɋѵ}q-2l@4-Va` _rRLnoᛖ~/'h׀!3_=d=0> endobj 37 0 obj << /D [59 0 R /XYZ 72 744.907 null] >> endobj 58 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 63 0 obj << /Length 1897 /Filter /FlateDecode >> stream xڽXY7 ~_>ٹ6(-ZI ȶ9\x7%Ej& -QER)rno~ UIURs0]mGg{MN/~|yCÑ˟1 IWJէV8'v29F#[]AwFoTk%Kl"DY0΁,|x6{uE 51֭6Q)m|xNET) lw(z4yTUŌLZ1l TS<+z9PJ]qTs;#{Pƙ@DJ%]c77;z b[t Ar KRTiBF/i-.4:Y֙ ީ* U(V`]Fῖ.VA2[tr~u0pG8&``},`b=ǣ*4qX7Wzhŵ 7 }ٱhi|TYiPbUÆ-.n\TI,)=z%4jmnjSvYiFp9l?`52 n ѷJ.č;%\Pyo9S 1hjm& ' l$\WޝENPƫ9GJ0*Č3N2 [ۍPf08Ѻ$AJa9iʘeqD"BMAi6)]nNB66f?sӼT)f1y؜*Q𢑍zT]xPg{+W:1 OeSyay>'  ѬjGf?K _|hM4Ad(0UHCOU#sڄuQ`PR-Dj-hTW2xLS'}͹Quސ'> N*+؍ӕ{g:Yt'Y(,mE#E@ۉRˮJ#$%^oӗD B-,_\_-ld HiZa9L Rpc(q+H(K |HRnpj{UgŜi<Xˑ7d-!cgIhjcׁP`t2SKS{÷I3ϴ(t_ryR0|G굇wUm' !{4.s c}Ÿ@2~yj[qдhh;n~sMCeoE}* yz4L*J3\-~X}:RtVnXqnH4cݓѬ߿v endstream endobj 62 0 obj << /Type /Page /Contents 63 0 R /Resources 61 0 R /MediaBox [0 0 612 792] /Parent 35 0 R >> endobj 38 0 obj << /D [62 0 R /XYZ 72 744.907 null] >> endobj 61 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 67 0 obj << /Length 2890 /Filter /FlateDecode >> stream xڝY[~?)-V$$E"-dMYr$9{ gHK^m hHgoeD]jpdi**6eTf7=OU٦@q%Aޤ$כ |)TT^ K/}L( .q=‹Dz,/DM"%h<_ru]w8D3XK|KW\B4k6ȷbu:hy#K}Rr}s4GK60=NJVA-t;-;87A h>G X W,q ^M;N­-U/ -w!M!-ѹ~AuJ"vI|P3gen./ngoI d#ݒnz8v:":T6^U ׅ5Ѱ,wF?0qS=k cIz]ϦpjCïzwIvo@'v𩗦ad+_ &鹟;j7vڏ펦ќ_pGg7_=`&ۨ<JU]֢z?I«p D/,?gZH7DL4f)Zeb(r%Fptt$$ c*v =IY <. @jQ_aq'$iLa1f?_LwPb񻁞eH,,3N ^{>g"W{=n`"so )juw8U>ɨVm1;yUO] SPE h֋˶H0%"!>|q*|H8[ĭKH |:"B^s rM hDgqC̑$>>jQU> zV*&y%t%ץVR 8M'ѕhBX%ZJgW0=e$L JўL "(jY|âdQO#2fN9? {԰l?7%p43)|KИ߭aw+l-vA)tw$*85:~l?z +Ͱt?"ML]xX؝cw\es>lX(8S'ƎNf& zV>N_Ҟ3 uyN"P %5"űH(+ua& xKieʟVC)GR LPmڎ{X 6\ 65{j- J041l>Ӽ5~A-^kvN\Gs!Kp y:\W}\#-W\~Yl@mOv>r,22h>gm0͍*+qXgج;')2<~Y`]!"j׷*k)-0L 'TO=xFa |0L-+Ioj0C&rS {j;s\O͙g*ev%\ ƫײ4xKxO/rWߐUNpeZb'8hN Cﶧe0:Pw{CUb?F<}3hmhXDiVߴ~J'vt/@:,!ץHer 7'B ׎ooV']?n|+w4ux] %痫ku8sEdI0hq&t&48>sBáI.c@` {4r̪,\ IQPj Ō⼄-Wo~3GZjY5]a_H[~ DY"j{ŵ0\ דL墪#e˗@B?,eūF.ʵPadˍ/w$;X&s]vijB:*f"C*i.Z|>TTN_ *(\LZ(c|mnJ,ZLA⦜?@2p[1s|=.]_6|1mw'w|O_x- %^i2-<{eDl ?4kz_ hpvPvwq;V&gvFHGbށ!b8}*6 wF˕ȋoӿ140 .d]miwn!Q/X U "[ "i endstream endobj 66 0 obj << /Type /Page /Contents 67 0 R /Resources 65 0 R /MediaBox [0 0 612 792] /Parent 35 0 R /Annots [ 64 0 R ] >> endobj 64 0 obj << /Type /Annot /Border [0 0 0] /Rect [420.448 577.478 540 586.429] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig.substitution) >> >> endobj 39 0 obj << /D [66 0 R /XYZ 72 744.907 null] >> endobj 68 0 obj << /D [66 0 R /XYZ 288.926 415.127 null] >> endobj 69 0 obj << /D [66 0 R /XYZ 200.899 299.514 null] >> endobj 65 0 obj << /Font << /F38 34 0 R /F41 56 0 R /F32 31 0 R /F29 29 0 R /F34 32 0 R /F11 70 0 R /F37 33 0 R >> /ProcSet [ /PDF /Text ] >> endobj 74 0 obj << /Length 2695 /Filter /FlateDecode >> stream xZ[ ~_EhnҨ)Z$@Z$iq-$Z{V-9= ǗR=$hї"H~3>JEeL6R/RGJKuYx'.Gӵ1۶.V2^[j-lrv+/Je~8 uhpSؽ25;MZ+m_>6m[ٜQf u8ig|%e;eFYdy|rsKݼOPS/V[zB܇OEC #Gn%qQ>Rg}=B$Q:qѕ ,+`/C{1xa1o1E9=QqU ^C^ TQfS:E&L{JL gOTM38J`~ĪV/َb%O%AN XK r+TidqhH L-G}KH= |M %ҷ+NY軆:$,9_לܦ):U@\!Q$R"2[P0Srܣ Bo^P o YsY9WM{ Є> kRD40\ZEPа1 WE<[\]T[LJ\UeYzZsݮ0Xw| D6Fi$^U?;h\p5@0̡;=wair4fjD޼okuj40Z15Mjy&QMhRZq ^kK3ؤ" _Fleqޯ &@d|șq6Ql)ʳ v<5[ 4>үaɔ`ia&uR5çƨ!z`g, A>SJǒY6o e<kyµ6&ᔧM`+8P ƪ>o߬$Gj?8zV+_=aW%_ VgLYDFNRuJ`#50 BG+h IۍNͦ.N})MM_w0g=oxsfNGGaB5?1>M_a1Ȥ}qgpƖ(1(c%r/;SEWfj t|i<>ϰDL_o*qA,=MA@5ԯMtѲ8Fگ?ڠ3g䩇C^ptBZX}3Wf4fR8k Z+b@a@eߵyӞk_=gف`n/h-$5$P!oG!kFj> 6/cZZJbɇ7A$a:l9d>7s*Qudk{[b|'oT$w UU~Ď~$IcRjb8Q*Q/YnČX Py}=Q#Mu< Ѕ> endobj 75 0 obj << /D [73 0 R /XYZ 72 744.907 null] >> endobj 72 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F11 70 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 78 0 obj << /Length 3071 /Filter /FlateDecode >> stream xZ3ÇRh -m" - gK$ 2A шyP_}RLdWUWBdu_}?~I<"$/!1X%gh]WU|.u2_lEb#[L8661R"P|/V[鱫]StLyB%]>:ªS^ZLш?m~(.#FZH{ ;um'H@LȊ4S?2VL-n=n"Qv]4]c{fyŞgv/ܭ}R7Kl&F/L]emjz~Ǽ b\ CY\vq.޿lLt1)H*/)S@DTYucp{.}(-:hFd`7 b&Dv1ƮI;cp{H]{d}~M{Gɴ0<v|=&ߺTW,D'R~fh낷";4.\t/]}ʻr?|,N`0J]vMWrFak|L/܉@n֞=dkf{mHbx.6ZS&z&Ǿ.rV Hib1KԺAK/r-B.Q "ALU!|h3dt؍ָ(8]6Чb:p h a ;Dd:к1 +կ iާ+JX'p-ELltF?6 .xt@6V izưS1lJy> cKkUذ>p.| g8؁3ŎZЯhYJ!#0dRYkص`Ř+~p7L",ZbA7h*Ph4'Qh4%ZqA>v{wa#8vN 8υ!Oqয়e ƹ@%38q:k0Z(>q#H %Xq r/ .b&qoseY,[0D( .E65gFC',d)1$F-J,'LHi&ɲukDS%S ZCF|- h 틃3˱#8Շnن !&_4mފl>_:%$C'L/ |f !$= '_\(ceEԫ-;c`3~"Ki ddI^Wl dov:} i*E&gN]'j@$:g$MW襨?bCȵGh9?Hx=Vs`rA UN+wퟺvʲp kSzV`L뇔:V`!6I¾:(=BIȊWu7W8#t%FK>:vl#pO +dB8 _LU"l?Cl~_,~Hv*ѷ45-^n)ZBY"~h kÝϻbȫn̡qNPܖC(P6.B6}()9cQ=2bsE&Aㅍ  +($ayXG0]035٧XVM}#Ju`T$RүƸ3K!Q&bZ~S(/Pp^Lgq(xSgNwp+j{]hl# xrS=&YJ}zuBܷHXp`1#,.Hb {_@N )Bo%xH -B9$7N)h_`$4}A2ZXO # КZʡ-O˱m C+ 1zE0@ҟ/0=Q mCTKå+ &jIa+T6 o9$Vs@BP r.ql.fVXQyToIΡ+a- 3ݲDF}xZ)1њ>&B_Q_Z•wnahKb:94oB 䎌ђ\Y/8ᨅw[̗5o| o4azt;o괜,h>v~ߣOl!8O^dJ4rpQc1‘ȳ6:j?)/xrk,YȸT W\}DM/^Ef(35O,auO9^IucpD"-0]J@FK%p/(ü!:<Ѭ{-KHqһp&ȸc|s^6PNuBՎq@PS?vOu}lpnFʼnczwq;fR3'jdw1VQz׻/Q[&FZ(obF {/cߎ.a<-f?b % endstream endobj 77 0 obj << /Type /Page /Contents 78 0 R /Resources 76 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 79 0 obj << /D [77 0 R /XYZ 72 744.907 null] >> endobj 76 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 83 0 obj << /Length 2578 /Filter /FlateDecode >> stream xZ[~_m&IC,ڇ$@6},Ė|$9' w!u.=O"/ÙoZVFCU(p_}.)p J\ŞӌNrIا۪+&1$r4 sV[-`aj1YGrUq.6} ˸ k/x,wJb kaX~~l]jM&a$a+~JrIUmWT;KP+B+8'=Wۈj}+e~NrW%lBߍݗEꪃjI 0% Q7(iwG{.f|6vf'MUڋݡ?ft]T9;YtC:1Jpp=ʏ0V-0)Z2yiwuc[xv[ٲ]ᕺ,'񺇢-w[\cbD ?^c@A? #ܥ>!mH?0u>G:E y~7!Hq<:RsŤޮ`'ۗ>5/НE)[J {S?[;? JX_n$/_ȖSL:íAaT{]!~r v:gI7ս]`奄4308\•#2]I7i+1+"9'v5ɗE9&IF{u C!G -ΈK9Q< ,ΰǘxNF9nHD)1ǐvה|9X=ۮZ!Ґ3T.607kΔpƦַ6yB#ֽP#l>Q"J|X!Q)3{L'{%P-1Ò3H | ) nwmp!yu=?fgo2@+D/nOzdG ySrtt\T5dNҘ7?}w??;W}wo݁=M?;dq>}vxj 0a>gK}Og ;M֓1㙿|_@1:~\'0tg|sSN.Ft'i@ZNzSs<2Ђ"mu7NE|5 w) _Nuf$jvvf"i73'7 hd =#iz@1P ` 䦉oR@f蘬3rם@`pɁs'[xgf*1LBav o=nqͅxO'd<ݖJtJD{|FTO $*B::maŸt= "`':BKZ C(ГEr> endobj 84 0 obj << /D [82 0 R /XYZ 72 744.907 null] >> endobj 81 0 obj << /Font << /F37 33 0 R /F32 31 0 R /F34 32 0 R /F38 34 0 R /F11 70 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 88 0 obj << /Length 3098 /Filter /FlateDecode >> stream xڥ]~/VI-$@,-9|;!%Ro'Spf8o1*"Jfi _~ÏwD$Z,TRhCy9В,ɥppo-"AlEd_mK rwpE"r*թjV|ݟ]K򪧉cEc@a 77\Ů?ۻYHHOSK|L6,2+[\CE(KL&!; $YDaԫ?2؊ܘ\eНNpAiVpBj]0з&`B[e)8&KJ)/tG3)D1msq&p6ܽ~Fyp1޸焣yH>H ba9[nZ\&Vn]eW g02S0sXݑy<Ի C Cf ]B4J=Mb4Lm[zˆ+W"[ߖzmļ: !F(KGO ˣO$؈qrOSƏb6b:LOӒ0թDm1+gf`+e cd[=3|`+0]\.2E@.T-ԒoI(&CrXY'+(rFz <:ۘLE"'-׵2W82jIqeJO񟳕iGS B,Ԉg^m%L:STYg 1Enn{Fj"K0tDlQS:ՆC<￶(yvTt@n݆iU ыNg[Ʊ {WJ<M6M TOoSѠF{@cTهWR%,>N:o 60kz|* L,KO0vIyN(P|+LjрrQM xڦ|j_  M;^|\ kG:sA386> endobj 85 0 obj << /Type /Annot /Border [0 0 0] /Rect [354.82 563.96 396.663 572.912] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 89 0 obj << /D [87 0 R /XYZ 72 744.907 null] >> endobj 86 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 93 0 obj << /Length 2822 /Filter /FlateDecode >> stream xZ[~?/E}5ËؠE4@؃v$@u,XX]'.3C)JOvHDp8_>}L6dIg\rr;+l\>bΌ8ݞ}5}s.idXQ+%y2͙f թBr[NMwʈs4f豺o'`y~wvo X.L֓H({e -fydMSHR,K9MILy,z{w/$,'Q.3\aen[z4Ͷ^l+tyٞ?Ty|狀Qj/~@@S9VβU2ӈ,+pIcd{W#~: 0@o` a; •jZ i[HY1V;;k?D4\jǪ]s Un Ϧ!ńp/&y |XOy/g5gx@yI tԧ)ȽD<6uMiڏj }ܪVQ΢(|c;}{K@5}s=g !gsWCs=EAb ^MMn]g 4}2絩_L} Eӑ9%R@Av%^> -"b5Q#GxX4'quЬ#Mց[#o_#iI`QMRadذU*JOIM?̮=&E`Ĕaܤ6G7LRkp;C`q&C(sB y>[l F.p_Jq|vkDV'h=Rg4"7G~(X<獨 i1 hm " yQg|AT%\d}er|XA:h}ͤsbޛ(? kBCC6rb hǾQywE>[7ʗleZoJ::y%e"~ӕ=}zr*ELN25-gR?v}c%5觨Pt7SIܕ9=aи.@W_$`?ZBo ⳩X bkYvA 4_2l +³dȀMܔ@j5 6+ICQ+#KƔ!c1kAkI,##tN}$ܲQL:bt2^q.}%ýB&%c7AO 3ณ5XVsъE)\tWߒK~Cߋ!TD-w4_S񃰶Q%¿&z 9+~t=A=A6h\}Cuei^8<ˬ.>orQ}(h -=Aڡ.)2۷oiϕem?TZ /^_ݡ~ AĎϏujF&89}lMQY䂉eEntwu8XǕ{뒆TC Cpj.ڄCFP<rSb*J JJ](& Yv%+~Y<윏jxI]g0P&=lV54/E |WJ.g"of }1 ?4#D…p؂$9,^5B!zo7e Ch\~GY&G{ ;l~㣧\S@_ڢ$.=weԢ69)b ?V+ſ%-I8wp^G~w B߈ s}{|SB78S-47wzC"?BE;տĉYYxUHx0@\t endstream endobj 92 0 obj << /Type /Page /Contents 93 0 R /Resources 91 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 40 0 obj << /D [92 0 R /XYZ 72 744.907 null] >> endobj 94 0 obj << /D [92 0 R /XYZ 218.844 299.571 null] >> endobj 91 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F37 33 0 R /F34 32 0 R /F11 70 0 R /F29 29 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 97 0 obj << /Length 2854 /Filter /FlateDecode >> stream xڵZݓ۶Bo:';;i'8:qL I kgDX.]Hn^bQ2n"$_U?>囤Ұ\ N/A>!HY`"&,/8VrBVeX`eά2ˡ=HiӘ=nh_mG~3{M T"sΠhcgl*e,;"5!JX".vL>轉mdy9'h.8+y>WʹQ|&{C h>kI-jnZ7>wwz~J|4MW[)u !"Q,4v@CW߯$_ITEJXE(͚ZXIW&?\)P0GX MOt;ӄU>KY-dN yrrD7\i6mPnΙ |pkzG+g"QZnRk|Rb\ XLQ"U2m zX4z|xf+mB׉6˕rlńhv1LexJ_#ݷmc^RhF)Ocwz} *Xv뺤%X&SUm8rH @qj\ l:gU&#!- 2σYQ un<+ъi+N%Xhy^Cd+iGxq_a2(L fWn^6B ]*TĎN P-z0\"Ď(p|հ{q{Pf g˴%S<03F 1k55C|r)[`۷&x :6Z]d _'{ASiӟ_[K]' dXFu H|((Qe17QV8}DGkME757` ӣa%Twkc =CE"xQ#qOXevY{1S6:iw<ş&KWu~S=/N7 T1` s@Oh C4w7?޼r,P?ҋI endstream endobj 96 0 obj << /Type /Page /Contents 97 0 R /Resources 95 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 41 0 obj << /D [96 0 R /XYZ 72 744.907 null] >> endobj 98 0 obj << /D [96 0 R /XYZ 285.355 358.885 null] >> endobj 95 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F29 29 0 R /F41 56 0 R /F12 99 0 R >> /ProcSet [ /PDF /Text ] >> endobj 102 0 obj << /Length 2422 /Filter /FlateDecode >> stream xZKϯm9U#.Y9yoesزɣbJfKBR̿O ]'Kjh4u1oo^LK~|YfBIva]-˒c5=t{Xx^KK)qncIojj5W:ɴ h5쇩;w81ؼe21Z(5}s='l_nN Qz时njЎ8w<1/WXĢ)c*þ݊,כjGd)M~Ey#!Tʓ43V\2T4,4[uEƞYJ&F=mA-ybE0ߊ ~ͰCSҰD˘u m,}#$Zcx$:YRF.KsrwNFO/ij&B9C(vgNJq/5DkuUx=_`T:5/CаhGa*̹qS,ګ'lD9E(ihNbLTzbձjI\OwglY7 m$lQAZ9OҪӞ֘c1IࠩThʤrN!"2Q2>.^xJ ՞Ymz)2K'f!.VTf?c 8]9bq}ۘW8=csMb JL}5uШ8e 4F, 2H@F}G7 k%{/{DtBDPd:0Єr&3344EP9Au 14@MK㩝͞lݤZHSE([#qSh_6^82-{!<93 )CkC 3 G.AL/1A!>LgprSEA?frv}CJ)/oIh(e:5AD!=dsB h8X;8{4X`@WTKJxA-QKYq_yŸN3c"n{mh[//?ˣx`E˒yK1B~98+D) 0y <\}: [Ġ&òZ4̲ &ݳ" [z 7I9ۛUJWd1vg{plwO<ǜBr̝߽!ze?c>-2řOW4}pszE(AFMVΒϣ B'>WUx[Evn(I@` >VCmm>j>LedOF64%iP@"1ػ* Dbϰ]0l>؊q. -8ז3vnBw( CX"Bef{[{=m>zX|!hE|pn=*gA8IIfAˠZLxdO6{1] %oXzVx:B++`/sScRwl m!":oE xWQ @Lʅ4C` ~مG%x>$ap~}sڛ,J/t5ҿ* cti_CE 7n>9$cQ k~f hr~5i4Vz-\e<kH4:_v?uu5nDpy7ϼ -hЎm/~EUF݊67 P{Z3A2? i&HH%]uhGS׈1Ə>siPvN]J m[-n#﫷{_yGwR?)X^z? ѮLrEPzΈǩj$bN^ì9w,K>hxh1ok7dيߐ9^m7?jHN:r{Iۯb#hpyeYTuu5tH\+\v/@y+;пKH endstream endobj 101 0 obj << /Type /Page /Contents 102 0 R /Resources 100 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 103 0 obj << /D [101 0 R /XYZ 72 744.907 null] >> endobj 100 0 obj << /Font << /F34 32 0 R /F32 31 0 R /F29 29 0 R /F38 34 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 106 0 obj << /Length 2306 /Filter /FlateDecode >> stream xڵYmȗI&mp@6E%z^\I=gCRW;p|o,OD7y0ݤNE їAwʹܪm˳`qĤLPv-UײMv\L@V}XBD/}Kߢw䉅 |A3TqꋝQU찅#Qp% ݥ/ڪJg୅軎juzylnSu.@Z{VLw-eyr{ =D?ޚ{ʁK ImPlOKRKxjTzLǮ'/`]ҫCDzXEQ8ʗ Fv=jѨUS_YЀkFU_E GYhjiw@x8 -/G\~2397\q‹p$$$ilH\03DD?ꭑ\I3qAC5V(ܟ.PaA48ؙEEOD$qhMzX0yWoK^|H\]$K+v)u[_'JΡɡJ]?̉+H !˩1\tnja/el# =fSzpԻd? uKK$Xq{6kߺi *i NV^#;5+KŜK8|y>69a>{C[u9X>wY`&qx_&9Q2\%,P "\B>FZxN>x6ϲWſIc6NC07a[<>Û=>_uDQR  Qz/ \,jC8Q"L9g\o5xG:/Ym-"g@k/5v~-\a, Ulaݢٖr?6 -O˔@,cȮvxi Hx%Rڊ> [hun ԻE_ec svܶ=\]+"?ôi]?Z.sI8- t1MfpIS2Lt'$HP`f6lRX x`6 ́N &wd2 j^vťq tla&&,H Y/BO5 r/j`-EQSnzv4u4ZFSF $(9U%BL(7V9c_d!E 30E%Ʌ*U\N [YB؎kv.l5LN[ F߾૚)Q,dN7[N|o(FYl5OyHsq b?[.|Ґ5tNZo3/L?Gݏzb]pR6"m, ߱>5oG(M5Gx5L > endobj 42 0 obj << /D [105 0 R /XYZ 72 744.907 null] >> endobj 107 0 obj << /D [105 0 R /XYZ 362.694 503.372 null] >> endobj 108 0 obj << /D [105 0 R /XYZ 192.626 261.964 null] >> endobj 104 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 113 0 obj << /Length 1134 /Filter /FlateDecode >> stream xW]6WDLG؎8U;UVivZ[M9@FHXJ_L4iW1vUTlj'-q8 6 6LՑHq&⮖ ,`˦$wzU+[SbeA$D}' y9dKxbhbn~p{rn3L6ۖ]u]>V!רrm{A5s@QiREs˜bϬVK16&Il57QYe% ߸knYY4O YeXlw՝rO;GS9 K~vqlYY5oy8uӻo?%/|7fkF}_?\aAz ^6L&/'O4  QG (OFh0.[&?^LE{1з5h(C[='!NB'7}vb endstream endobj 112 0 obj << /Type /Page /Contents 113 0 R /Resources 111 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 114 0 obj << /D [112 0 R /XYZ 72 744.907 null] >> endobj 111 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 117 0 obj << /Length 2313 /Filter /FlateDecode >> stream xۮ|@IQCm-r (mӶr@9ӻEq D|2ezX)z}M+Cs`XoÎ U!:D¨Bw]?oM9|`vqʱƋdS{Uem{o=] % V= A&=KE2e M FFA.~qz4['87%\Zb39wp t}'Q^D1%n;@궣BH8}Ԩz)\rH\'B<}P-xc_bx+++)ءڎ ܇ahq}[l m{H&@}ڕV,##jެH # {, M0mM+6̀5$eD6IΘJ]/iZZk?]Ɲ; çLe"-ZDY@+:wI,X%0їE"y1T}!=㽓x66hV[Gຎ@`n#m''Yy0{b/PҀyqh.Wa4[i p)6,ao (1$0:-ClCçnVT% 1񵖪2KCV]ܻ%|Mk`XƋEM1n*1íkv hz;}OjY,bg3`?6|s ~PŲuFVqyXJ<ϧ׃^|L]3Pݦ,֕Ƭ`=N\@;/nY8sGoth5iY,tkƳW|zx,cY^yyWTd3S>WA)lšf4Ul:ey բꚍUY {a$rF +r'sbh> .zIãFo)+ x̂mVup6xE&sմ+y(U2cGܶٷW U X J;G‘-{{D:q3HSs^Տw/c ,;Aq;_[uDxOhFC 67[XNv (^s7'@З۝[ލKѤiz:S$M[5A㋃owo<ѐo=@\*̜docsys oER>CUmT]r`HZU+M=}3>t4P>#ɂC*cNJĚ! Y։8 8oAE[0,hsޘj%6se-Cs=%/ύ > J?+X L];l5>P<&LQQU ,6ђsrAߴ'4{΃m{W_V36Gks3CJ-fN+O?!C@G/V*O$'c\,o+z"MG2 t endstream endobj 116 0 obj << /Type /Page /Contents 117 0 R /Resources 115 0 R /MediaBox [0 0 612 792] /Parent 109 0 R /Annots [ 110 0 R ] >> endobj 110 0 obj << /Type /Annot /Border [0 0 0] /Rect [448.323 683.531 527.526 692.483] /Subtype/Link/A<> >> endobj 43 0 obj << /D [116 0 R /XYZ 72 744.907 null] >> endobj 115 0 obj << /Font << /F29 29 0 R /F38 34 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 120 0 obj << /Length 1486 /Filter /FlateDecode >> stream xڭWY6~6*1M" }$Xh%zDIN!g[#r8739)K#/rvN9sW8~//neC7ݻliU" cjZ懻As2,w8*jv#U}Pl ϚkP~O,J+VűVm#ʍ:Ȧ睍,bg#KfUyHܫd/y+^rʆ^sz;Y٫L'sZRkV8~b! $޽TgRB\6?eE)R@=hDc&,Zp7{ՖVN DXh `7 A/Cc!wI-^6Mo >ꎇj3,qPyFf^RXVH=p!5u45cfIOzV'|zN,^m,^Ek Ek% //kz[!&Z EyaZdb>H{0_aB!}F2@{u=p(}kuĹ{O٣z]!͕;ξw{Oa'ltXEEtl`! #2gB]9U:}[>T^,|,[Y\dg-ބ)4,,IZۖMj7~Ӹ<8z+)[bfPĤ c"3:V@kLt:i9G3ը&y(졒/ F`*CHEKO]4Sv.c:Qf;FܭG.D"%ӹy"lhc͞aL(f2\ne='Y#F\ՇJ^\nl{{G4!\WW9et9(p]Mx&.;8"ԠA헬>kK .hZ?:@,ӨNfKk: ./h}fa]Mn].fPYYX7L4.r;=u?/%mN{nNV`pGD&u(eB$O7K@QᑿNO2xsP0O |Bzdب6[õ)e3Zi(vD؀;EfCGy1? V|_VW;B4W>pejj'0rnW^oz/B/"݅". = /Y _(}#L d Cniyq/ݿn} endstream endobj 119 0 obj << /Type /Page /Contents 120 0 R /Resources 118 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 121 0 obj << /D [119 0 R /XYZ 72 744.907 null] >> endobj 118 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 127 0 obj << /Length 2128 /Filter /FlateDecode >> stream xڝ]}^@> ]pAS4ȵ@syJZYIm_)K6wM /}A)O!x(DӖEe݉(GEF?eW5߲y%,<=^%:辕ݣ8὿ l8+„1WKu*A~bF;Ҁ` 9A6k8NκfOPOҘgWW3޾\S S#,l&rNGT9/<,~XF Ze5 [>c`O qxL$”DŽ9zzY|o߼%|H@_lW%W/9P.fP 팅xɔEY7W'mdC_Ǻ ųlExГ,?)EdՕ71Snᑖ|u6"OD|܍'x׺Ao `J/4_/=\Sl&ϊ';?yDkXb g}eZoT,L8@, oklຜ g鹬A aWr63E!@KE EP2cnO3Au#:AH+XSNRl"xеXNـ=ii j/fguk[UJA}.w&bvf"-('؆ۡ]NI*-Q7r r땳iN-mIv-]cCH\r$i4}^s&ЙjK!=ya=5һwyY^M㤩9ŋ K7Zd=¬#uDp qL!@>5%Zmh$b!NP!m}{#9'GS@`#I ƂN5--+Bfa?VEw[y<|!y*$ឝ]jc]v餬kq9 jm^KV*#' :\GթbB%3 2%pG6s>zIZě{*Q3:}F9"-Ҏ+8ŌvEuJ M1 j<c3!,rz:f8#fj!Rצ-E*J!+mh̦UopsZ B0ލM5|[7 !OƴNz#])󸊄/=0/ƃ>lJ؋ *ǹp@ˋp;JMC$ `/Bna"_J]J4X\iPn%bƿ\k{@Gm8%.jUÀ܄bBuSyHߌ+_;C')&y?R\W#^?U^U^]pOBs @VE;` qVm@?-^\^x\w;EkY g:;6ZÖUܥ;non[LwuE0tGЪҹes4g 7=atQ?M6k0}Ɯ[gf~_lP6@f-O$y/QP&> endobj 122 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 412.958 237.997 421.909] /Subtype/Link/A<> >> endobj 123 0 obj << /Type /Annot /Border [0 0 0] /Rect [314.459 401.003 417.422 409.979] /Subtype/Link/A<> >> endobj 124 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 383.378 215.391 392.33] /Subtype/Link/A<> >> endobj 44 0 obj << /D [126 0 R /XYZ 72 744.907 null] >> endobj 125 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F29 29 0 R /F34 32 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 130 0 obj << /Length 1661 /Filter /FlateDecode >> stream xڭXYoF~P]!A򐇢>@"Wk Bޙ!EJt}εz}wvαg?[{˴`v>6ja8šO2f,Tr۫m4KhQإY$46 {$hbv4HLlcQtT[?{ƶǺ@ `c͒\ndt!+eba{?30##ray[9kڶ"U3aЅmˢiZj al6O w<3isSM[fdYv^,FUl[!sUF.4)#k_ڟBP{fAM=XYMƍI_ ͛JDÇ7eRCW_Y՛Ő ÞVYY>0MfA҇+,;z8 :d֫ T\Vգo4  @4mFWn0;EśHTc6ݗUl׎/zs*'D-1@J1Q7mɚF*0Y$Jh5,4.^6剾9̈́P4/ be2IRNf)7AQd3̃ȥ`?TCUOUy`@LOk o)_ce)?Y?1s`V;AdzF;2CZg):̵Pw*\6D,\kW?.+H㪭{iC ;}M&c? =zK#lVi )T`MZM.=;@ݰXq?|o?elMꚆ)' 9A?we5+@Yc_RNWD7V^r6-O8f,8zɠaք1/;CW'XєOv׋Wyt#GUVo0}EmU]mPpi|I/ -޸b|wvKu;`؞ mId ` nh'~f lCNv(>fg?whkBsϟS,pkz\c'hNݡ endstream endobj 129 0 obj << /Type /Page /Contents 130 0 R /Resources 128 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 131 0 obj << /D [129 0 R /XYZ 72 744.907 null] >> endobj 128 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F29 29 0 R /F34 32 0 R >> /ProcSet [ /PDF /Text ] >> endobj 134 0 obj << /Length 2430 /Filter /FlateDecode >> stream xڕi۸ E120fD6bgb6SI6QÑL_wI/#͋@2*^=VZ%~ޛڙFE=pt D|iZh0&13:zvuˉnϼ|>i/c8rE]]TdcsS=c. GG,{iAծ %Xm(Y1;]L-”ĵL+΁C ݶ "GyavH(9rs.,)rkyӠzQ|*x/fZmWV~ b]}u<7.ٴ`@F^F?#|ZK{n-ɠAp:ApvIJөOM; /@'XYdlgPX:?wr{)]嬔w6U qg**2K.כcie0U7 esjUFMesZ|;3bxu^}nC6Xx'Tڛ!< !.7dr>=Rz3 ^C`UdCrT7( +MW)h(&i 疇M3I4|CM@| @z'dv.2|&D iӍ3*(Zr>*6Թx/,M`3Lpk>ק\*HӾȲo&I<5ۗtT l;uAz?KTT6`]ep8H @BHJcyp;BBUHp Yj EwXiގ:-zHŒ-t˔v߿1C~w ȳ] A]pEr1?l T(rRH^0aV 93O| p,dнvâ1F-ɞiF|T*lP)F94HIavy,0<_R=᬴uUX+&H4 ޻D̖! DeYGiS!-b"ʢo hw9z#\#U0jkKz3㹵KvO36ÂƮ||pW']y7 OTB^¨+Vonv #nC׮*l'ۗUY@ endstream endobj 133 0 obj << /Type /Page /Contents 134 0 R /Resources 132 0 R /MediaBox [0 0 612 792] /Parent 135 0 R >> endobj 45 0 obj << /D [133 0 R /XYZ 72 744.907 null] >> endobj 90 0 obj << /D [133 0 R /XYZ 72 238.745 null] >> endobj 132 0 obj << /Font << /F32 31 0 R /F34 32 0 R /F38 34 0 R /F29 29 0 R /F37 33 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 138 0 obj << /Length 3422 /Filter /FlateDecode >> stream xڽZY~_`un2ol;CłcIjS/F"/bꪯwAEJRqH5IYF f[կsuE]\%֔9Y}| 0)\9ͥ_j_Ugwێ:Qp- lmSaDS~cux*^֕_(KWaӹ mIbr$Yvh$yV1 D3뺦ج8P'dJTYNw N#\,=N.-6Q] 7vjvo2 *vy7ǢrLiOE-ϻvYkݲE^ "g=[smںlNq-pijs  *UeܧyW[xWe}ks2"?d"+Gs Q@[/fuv-O:avVHуn( ʖpRkeX!ʳoBF7s|*} dUQ=a=4 T:s!L&Eۢ^"Od!@|Q(Yڃ@~.+e{0m;fPgY؁vKX|nf-%c):P8m}܂ kV,Y>Q%#0NcN;TyCGqJL)W S/ _>쪛N%QQ(ᩅ9`5wc??N xKȸ!PE:vXTn@*%cǶcL!bltr9>~M>-SY 0;3@XaPu @,fb28u5⺂'uC`\7fu$؇U($)I1vVp T"5ǙPII~;ͣ1pk&3CtTƥQrω_)%Z0?^5FX)Eo.j8ĩB)cES(1\Nz (" ^.7!S>,=.!م+Pm~B 7@&p^)=L#&i,kḐ$IzsGC?mwڠv%MRN=[xm]g^WOQN! Agt.Xyjj$38= 0`wAZ&m?|l C%ލ\ =I-@`Z]'>/C8GLPh:GؚN -v[c6ٙۉ{P4363bC]{ `WѢ_.U}MF6Ya .ـ}.2X]%1G~~@p*`caݮOry8tTGj)^9&"%"+~җS޷MܤW "7XU^ձD`JbQCZKޫRҫ2K z~oZ3~:L ,/LM^#y0as D\Uhe{x(Q\'Ĉe`)mxm;[>=pV9d>!Z!?j0OeVeC8NQ"L _'`g 4$~N/zXj7ԠPҵwq\X-56o(n6؁RηVށkŨi^[G1μYbl0cc|yOla Xh*/bm?-(Hll{\|x;{g=^ O5 Grbif N][RܠޢfE$$Íy,J|`zyl˳UG=Q !&pe  [<LNR615qf2+qÀGhvWVg14'\"k@ u:KNF JcUr3,{) &DzF)|6#vۧ4!=Ɖ1de~[\_ >Dμq ¤:#e:drb'瑔Ԙn>ٴ)jw 4_I 3Em"ډ5s<ĿsHsI, }kw&!&`gPo[גc ׻e cFޯ rLCL04xH}RÝMmL]!CsaKܒ aYRi +a?u9Il;,8ν;7sAGtGBGbeL,1IK6JR&n]hp``ۄmm4ƅ;-xc񽇈) LnoPZ|C<m/3)sфS2DPpާ֭*f4jNdp/&y+㐾>Foȍ ,J7qSE2rCq g/ C'c)IS/ݡt&T UاD ޮYNJl"K}'C Zfm&w&޽h{=/H]ضۨ?2ޜkY9Zg2ӧBNI! >aIM(kr+}  }0ӇW7&wD}]1w) Xqt]߽ J M;Oe7߄B&L VV/?|Q0B= 6ÿE*?:'Ϩόp x5:%WdmX9wp JÖ&n6\Mf\W Lk-,8\`X64GWx– N endstream endobj 137 0 obj << /Type /Page /Contents 138 0 R /Resources 136 0 R /MediaBox [0 0 612 792] /Parent 135 0 R >> endobj 139 0 obj << /D [137 0 R /XYZ 72 744.907 null] >> endobj 136 0 obj << /Font << /F32 31 0 R /F37 33 0 R /F38 34 0 R /F41 56 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 143 0 obj << /Length 1568 /Filter /FlateDecode >> stream xڭWߏ6 ~*ɿ@u0`\:vj9w~(Ľ@,KI}HQo.AT:(RV,g<ʂ*xnߗz#`IT[9B3eZS$3^fwKwu(acEXv]7"[CO;sk]yx{t?h/ѷ2 u*au~Sh;EW^v5~sKcҵz1Yu4Nԉ]ܒh:Uьy%Q O9qSnVђ^;mT U6-WI" (6 CCr;m2 SLy$l8tY'pwJ_("[[ [77u{KCTڬ=K[Pl"׽.lN%˾ Ϗ1ǘ6N95A#끬)3 `6T")%"#0˯?XLTST>8u}MC|l a;5lXUpDۻw-clYm0ͣ\G:z yiQ[R 6~  L^-c/|BF9aPU#H%kAy>N%W}" uцoe eAt ċO@(\~AS^.,CT ƩԠvзGՕ^}3\$^jλg#Wbw7"eY&|>5)Q#n=m 'QK'I87(y>ӵoD3t"qp. ׿d,BXTݢ8]Mpy%> endobj 140 0 obj << /Type /Annot /Border [0 0 0] /Rect [155.448 607.809 197.291 616.761] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 46 0 obj << /D [142 0 R /XYZ 72 744.907 null] >> endobj 144 0 obj << /D [142 0 R /XYZ 72 111.464 null] >> endobj 141 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 147 0 obj << /Length 3334 /Filter /FlateDecode >> stream xڽZKϯQS5 | 9َ]ٔ8}PЈYɯO7 .:x4nǻ/MFF2&gq'qi/[62ʋ|S}9Yxo$ߪ{oa_7Cyju,[|moUT7KS:=K]յ8{VPw-Qe0ɬbU3b͋ h`Q'hJ/U4S*ˤxݡy1UDy:hT8T˔y-I=;[TYփZ$:<3To?MVLE{F3_zNkE"ݑ"D2kG蛾&#O8H-{!}=u(m4hC`;ME 9ñ=]"0쳱T@ gxήYDi&}ǔPrK TPZ!R\ e-`30 A2?,6=8+HYo(g-¦WV45 L5 Ŷ4졤W].(f=PHwdBz L(Fnf[cR'`xQ-0)U+Yjcц 2_ECf\CjЁfld7^_$MNrmZzTYP> O iԌfUuCpB,pxi{L xrX$0YȾøb< 0p&f%,禩&c^J_~fT&U 6f}}@}WYYY tY-[ ŭf[Pݩ\ڔO4d+^FaSBiG>/X룪]sNq5/ѫK` z-W+7V~fC(N:t'e0)bX5,ʊ?[-r{%B#-be)M[c4H$?Y6]Tmg'xxF=~mbg,8 ;0G,VQRZ*  zœ,brK7I̖dڅJ]kNnx 6 8f]+E]Jy"u4@sp$%VsKB9 u:r]'j'Y,Л[?$mJ )yx MjJxk8PЯ'7L(VFP^I_,*T`S2b V%ڡX$ʖVעdo/ڛ3 V,c*O~]Ԕ7΀v>2%Kb3˹H3*C,R}4xu(r{ % ~-5P07X"Ak`2-)0,8 H-ZWs,5@ohI6(M/牭q L`7Er݂K~+NqiTγLG _gZ#wmVv‰[8I zFNf"2L3nؔ1hkpL}8]L_NPchĊ\㓠PW~X]gNvh $YzC:\VJ}lpb;Xɪr%+ _ W?}0QinC9eaO֪Mg^Sa}5`?.*F1џ&H([ӭУ JY 09cq>@EQ3Hԉ\x`[zvg$l x :cPZ*? Ϸߙ;z>NOűYwr0M0h0㱷#MfqCWXj<@xCivmV.]h+:O9.=o%JcbY!jWMHx,0=mj_XSor`n+Q5ݠ>&V0LD1" +1A,8^8-s~!3^aR7tQ-s$ͣxᰗ °> endobj 47 0 obj << /D [146 0 R /XYZ 72 744.907 null] >> endobj 148 0 obj << /D [146 0 R /XYZ 72 182.117 null] >> endobj 145 0 obj << /Font << /F32 31 0 R /F41 56 0 R /F38 34 0 R /F34 32 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 152 0 obj << /Length 2937 /Filter /FlateDecode >> stream xZK6WHmYX)*:kam85 E*$_h 43j6!ݍn7AUr#f ˹]YWvu[}I}$ۺ{ldvWŮ쨳wz('7߷zGv[?iC U1;z:~\4y]YR4x/n~Ԋ \qETۖ_y_&2N6*LZ4$9OڵHݮl;i]aSO;Uqt[W[jˏCm#,3Rd*lgdZf_L&|W+UP46 JYnmoQ塈L'sfe>.fm*"nV^ll+C6ԉ=u>€Ta9k2"FۤhZt[R?vU_YhGSX2EQ=T^ԐH-lixO; $$Эf"$gL9\,H\$>w$x?f]ٔ]QA>>㋪nf4qqw4ᾢn|uU籨:fp6. ?ijpƽC9!Lw7m]k%/BBb"˗(O?NLWхN]a&IN~<"SG'lK]@IbRG,ƺD8'h]lDrXybEVŭb529~Z8\յ\2e9WvaRGG^SlS<. qD<;U sO]|"η$ :% H^|=t!%p܁ed̤zk,wZ_lݑV̮XܔṗUy#w~WMvo8^xдfF [֫xIY7]~ 3=Fd?6@ϲ+ /DP `#+I葜Im@TD0"yZ G<딩\_Kb|9ɼTω :JL4pBll/X,BT<3)U'aR' k5(c&r@yG ?5 ޷~^[On.qxߏ \j]eR zZ0Vox5ͥ_w4/p HAleE tOНB 4ddR,|i+Syҡ+A_:/>C"\)0gYH`Zâ[= DbwcܶZC& _2K*7 䞛} lyE *6z@Wtzܕgs"wj<V'c0nv(ǶH)f'^s1ƒ&̐YPR,M A%"8b)C- (>I}3s_PHES GY >w 0l FIwϢ>H8O*WRnfP%>i ZJ=x\>0Pa/#h|΄o #܄|dr.B:A=L=CtURewP6%ƳLt`iv?Z(ja.o (( (cr?R]28(  3Ğ!/.$dRٕ% |bר[ʕ$8).U$Xuu+> endobj 149 0 obj << /Type /Annot /Border [0 0 0] /Rect [399.528 83.698 483.214 92.649] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig.loader) >> >> endobj 48 0 obj << /D [151 0 R /XYZ 72 744.907 null] >> endobj 153 0 obj << /D [151 0 R /XYZ 72 100.488 null] >> endobj 150 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F34 32 0 R /F37 33 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 157 0 obj << /Length 2737 /Filter /FlateDecode >> stream xڽYYܸ~܌xr l"Y m =-ZձA*^"93]#/-X~{q)Ilslӄ$<4 v,MԵu5C'A?OpEmcvN'Y}8G~+r|mgC#3Pɶ߁᱓lv4'l˽mA3حv2˾_~0TG%ISj%+%e^=S 䱊J;M~v4e$2PR 7qg Tk aq6-ZpFA%4fnZNaT\c74RG̤t<72L 8. %8[DECf m=F{У+ =38\M SacN@jCV꾝$ߥNXSr^GzkƏ"zO9@ 0A"c 9nw}#HJ4^F)vKn`q#L}Ӡ9_?&@  vh؛gFg'wiN\/E8IvQdNxEr p5`W걽F h""x7PpqдdI "!$$jnyߪ:DE_]POIP7K>]gg)ia<"!y~k45к(lRnakTGRV7āu2+Gm`pkvܵkp4TS \)\mōojq`Iʞ9-#%Mvx<#B! | y0ƈQaV;⊄\ (8~9Z[[TNr"%^b2 HA]-,ބߪ{&Pz7C,$sT;W"~rޫ&lj;Jͳt%a2{{9cH5hAlc9\,/3(.h<),ArR̦\ LlC c/1hW`1z& fk=]5zI(2.+sx:Ȯ5 ~kΝC dm>OOSȥ]ƶ߫(T1is #G S0s`tkbV^:"4x5se gB@(si`O8$jkN,@IJ&VV/s H[PYN4)X7sfΟ"ɰ4|A0 m4m$R搱WYĤ}Bod$)hR/*(IjA:^>ROal Gqc)aI~aҙ80)AY@fY>͂SgguNz+'do0HY < x i2tm i OC 3Gs0* cz:SA'9}/ 8_J2e *]zwj[XK$ OCd"pRbe'U_~1Ďeg9AtlDz$[C5V5l?kN\}Jw 䞠Kq$䮡_-*Bǣl#f+qiRF#%u(BU9'cs"SMVbf3R4]JF" L{U'C$_UJ' ^Y oPPAY'G' ȯr~E /8dq6ŧЍK*M{"EL3aCGRbH;D#_S=5[ Y0QWPkX4c&\{e>ZYCU -JÑab%7(sY+#3@|jFuoTp'q0_[JeG c+K δeR ;jGCz>,M;N+kEк/GWY}Jizyӗ[ s9? kʥ';/*D3R*_n%`rg]}q.,„Iݍ7V-vruc|3|WuSq("AX.NSȊ#d-Lv<_8Ovԋ~Oaé% endstream endobj 156 0 obj << /Type /Page /Contents 157 0 R /Resources 155 0 R /MediaBox [0 0 612 792] /Parent 135 0 R /Annots [ 154 0 R ] >> endobj 154 0 obj << /Type /Annot /Border [0 0 0] /Rect [112.158 607.187 154.001 616.138] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 49 0 obj << /D [156 0 R /XYZ 72 744.907 null] >> endobj 71 0 obj << /D [156 0 R /XYZ 72 520.681 null] >> endobj 155 0 obj << /Font << /F32 31 0 R /F34 32 0 R /F37 33 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R /F14 158 0 R >> /ProcSet [ /PDF /Text ] >> endobj 161 0 obj << /Length 1316 /Filter /FlateDecode >> stream xڵWYF~ׯvZ0 r9vIi?hK¡俧iavcU%0G_4tLI0DגE$KSU$>Õ֗5UUϽ/aIXH|:mVp.UuCU[0x/.Tl(j[y&ntX+bc8GbWxy);{&vgd.(җ7-Ǔ4^c+$\ u^Uyz}cg=@HFO̭WD|yZ }` + ZSbݲ4IJGyMYVkD=0 *:"@KqtS̀ɓ$s݈瑱y; bhn}(hyb:`v oACW~xQ2~ QL |H@,@`}_=ME]b܈t MEѶpi f"KlZ2ae{u vM=QFWg^:ʲ du׳ _GFz, Zbge^b Wln9bb|)Zc*60ǯuˁkGmiTHuK1 PVBX]ӳ ]EhgPS7]f=P;;ا 4,pqLF#0  c_*4=uBg?ySh( ̽IlP +NV=ƑP0j=˪usb>ޯqr5o2:n05.88ȥ.~{@*i(Ĵ(}J+80( DZ~]?nt44Ҍhp endstream endobj 160 0 obj << /Type /Page /Contents 161 0 R /Resources 159 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 50 0 obj << /D [160 0 R /XYZ 72 744.907 null] >> endobj 162 0 obj << /D [160 0 R /XYZ 319.828 533.207 null] >> endobj 159 0 obj << /Font << /F29 29 0 R /F38 34 0 R /F32 31 0 R >> /ProcSet [ /PDF /Text ] >> endobj 166 0 obj << /Length 757 /Filter /FlateDecode >> stream xW]o0}ϯpۗ0 6R]>PpV@R Z+`0hMSb{'c6Sb#,x!.  _cDvFG'#>Le'vYժ{^ex_Ԃ$ \9r#3L,Bi4ˣi'.E;'/.7ŒAǡ*HY *ŕ1vĔCyr*U40-TycP$<{?MdmI8An0O MvgP,qЌe HCIe ȸh%jkDIXa`uFgM6K8o_Zc޳C?! ]LL#R2 PwTZZNԡgQyvx'%~͝%tR ڴS݄L٥NݾJbҺp5R 5A+@f԰h{?zˊo$ȴfn2uzc2- XLݗeɖ9/(7lY)#6ex/t/~/6Yl Jd1eUC]׋?뗸V\ʁ X#Cz \\!i:9Q.͸`xzV<c L)C.n*}54== P_8!D" endstream endobj 165 0 obj << /Type /Page /Contents 166 0 R /Resources 164 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 167 0 obj << /D [165 0 R /XYZ 72 744.907 null] >> endobj 164 0 obj << /Font << /F38 34 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 168 0 obj << /S /GoTo /D (page002) >> endobj 170 0 obj (1 Introduction ) endobj 171 0 obj << /S /GoTo /D (page002) >> endobj 173 0 obj (2 Configuration Syntax ) endobj 174 0 obj << /S /GoTo /D (page003) >> endobj 176 0 obj (2.1 Extending the Configuration Schema) endobj 177 0 obj << /S /GoTo /D (page004) >> endobj 179 0 obj (2.2 Textual Substitution in Values) endobj 180 0 obj << /S /GoTo /D (page005) >> endobj 182 0 obj (3 Writing Configuration Schema ) endobj 183 0 obj << /S /GoTo /D (page005) >> endobj 185 0 obj (3.1 Schema Elements ) endobj 186 0 obj << /S /GoTo /D (page010) >> endobj 188 0 obj (3.2 Schema Components ) endobj 189 0 obj << /S /GoTo /D (page011) >> endobj 191 0 obj (3.3 Referring to Files in Packages) endobj 192 0 obj << /S /GoTo /D (page011) >> endobj 194 0 obj (4 Standard ZConfig Datatypes) endobj 195 0 obj << /S /GoTo /D (page013) >> endobj 197 0 obj (5 Standard ZConfig Schema Components ) endobj 198 0 obj << /S /GoTo /D (page013) >> endobj 200 0 obj (5.1 ZConfig.components.basic) endobj 201 0 obj << /S /GoTo /D (page013) >> endobj 203 0 obj (The Mapping Section Type ) endobj 204 0 obj << /S /GoTo /D (page015) >> endobj 206 0 obj (5.2 ZConfig.components.logger) endobj 207 0 obj << /S /GoTo /D (page017) >> endobj 209 0 obj (Configuring the email logger) endobj 210 0 obj << /S /GoTo /D (page017) >> endobj 212 0 obj (6 Using Components to Extend Schema) endobj 213 0 obj << /S /GoTo /D (page019) >> endobj 215 0 obj (7 ZConfig --- Basic configuration support) endobj 216 0 obj << /S /GoTo /D (page021) >> endobj 218 0 obj (7.1 Basic Usage) endobj 219 0 obj << /S /GoTo /D (page021) >> endobj 221 0 obj (8 ZConfig.datatypes --- Default data type registry) endobj 222 0 obj << /S /GoTo /D (page022) >> endobj 224 0 obj (9 ZConfig.loader --- Resource loading support) endobj 225 0 obj << /S /GoTo /D (page023) >> endobj 227 0 obj (9.1 Loader Objects) endobj 228 0 obj << /S /GoTo /D (page023) >> endobj 230 0 obj (10 ZConfig.cmdline --- Command-line override support) endobj 231 0 obj << /S /GoTo /D (page024) >> endobj 233 0 obj (11 ZConfig.substitution --- String substitution) endobj 234 0 obj << /S /GoTo /D (page025) >> endobj 236 0 obj (11.1 Examples) endobj 237 0 obj << /S /GoTo /D (page025) >> endobj 239 0 obj (A Schema Document Type Definition ) endobj 242 0 obj << /Length 205 /Filter /FlateDecode >> stream x5OMKP_1Kssw>/*[ALDMiDDǻxafvfQUl<"&tߞn׏U-h^v],z-WږkjeO?~}Ԥ' ݸb0-hX-( "^f%t mri̓YLM$#k S/JN,I F\9 endstream endobj 241 0 obj << /Type /Page /Contents 242 0 R /Resources 240 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 243 0 obj << /D [241 0 R /XYZ 72 744.907 null] >> endobj 240 0 obj << /Font << /F38 34 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 244 0 obj [500] endobj 245 0 obj [666.7 666.7] endobj 246 0 obj [777.8 500 777.8] endobj 248 0 obj [500 500 167 333 556 278 333 333 0 333 675 0 556 389 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 214 250 333 420 500 500 833 778 333 333 333 500 675 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 675 675 675 500 920 611 611 667 722 611 611 722 722 333 444 667 556 833 667 722 611 722 611 500 556 722 611 833 611 556 556 389 278 389 422 500 333 500 500 444 500 444 278 500 500 278 278 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444] endobj 249 0 obj [600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600] endobj 250 0 obj [600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600] endobj 251 0 obj [556 556 167 333 667 278 333 333 0 333 570 0 667 444 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 278 250 333 555 500 500 1000 833 333 333 333 500 570 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 570 570 570 500 930 722 667 722 722 667 611 778 778 389 500 778 667 944 722 778 611 778 722 556 667 722 722 1000 722 722 667 333 278 333 581 500 333 500 556 444 556 444 333 500 556 278 333 556 278 833 556 500 556 556 444 389 333 556 500 722 500 500 444 394 220 394 520 0 0 0 333 500 500 1000 500 500 333 1000 556 333 1000 0 0 0 0 0 0 500 500 350 500 1000] endobj 252 0 obj [556 556 167 333 611 278 333 333 0 333 564 0 611 444 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 180 250 333 408 500 500 833 778 333 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 278 333 469 500 333 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 200 480 541 0 0 0 333 500 444 1000 500 500 333 1000 556 333 889 0 0 0 0 0 0 444 444 350 500 1000 333 980 389 333 722 0 0 722 0 333 500 500 500 500 200 500 333 760 276 500 564 333 760 333 400 564 300 300 333 500 453 250 333 300 310 500 750 750 750 444] endobj 253 0 obj [278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500] endobj 254 0 obj [500 500 167 333 556 222 333 333 0 333 584 0 611 500 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 191 278 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 0 0 222 556 333 1000 556 556 333 1000 667 333 1000 0 0 0 0 0 0 333 333 350 556 1000] endobj 255 0 obj << /Length1 771 /Length2 1151 /Length3 0 /Length 1689 /Filter /FlateDecode >> stream xڭRkTRIzX%r  h <$f&dJ2C $ J,bKTXUX[iEVEK5η>߷g%g l,Par T&r؀<-aJ@V뵀B !OP,݈#'E| 8R@$4RjS!0adV O τ!&"&8A)IORT;ҧ2a<4|liP@Z`VVl?xT`t=@A0N`릳RBET"4U g%dH E!JJm<(4 ߔ<^i(%tRO՜j2$16B{J6L0AS84R%"+0qB1by5S&Տ XFE06`iጌ)L\QcPq%և }FLa([*`Wuwg,~aiKs7K4]?GND~ǂԼ>Q$gmq:{+g=R '&ϸRŃ鍾~~~/cݚ~%upf[m9} yPή+}p U_xnwg\&si |5M7;(imqr~7`q%.)R.`a4?$"V3xf60j׷nh 廥wr9hS_Xk泤/{_5S[L^v'g ׿z+5'DNmg7@fǭc_y۸q6~dݛ(6 /W>An$47\m+ k f&Y$%M^A 溈fR|ob| ߡWCXG-uں m7ar"kS?[f۟v&fD0gqAcCygex|9j¥(ee >ҊqS&gyo?\mڳѓ+H: /Krn ;l㖻?![BU\Wu-A͕Z1x%Dc>Dv+wgi?/bX/.qwC# 廡;QF+&uMܯvvvL-{ ߗ2_e4tj*]Rnel;k7g̍[kQ7ܯE(uű|s WS_ucu}v]ΊzGY*.ycj/K<5{/U;І;[WNq9.[z_"F<=g),x9=_^,)ݺVۥm.-+[p#W]h.#^I8{cJ'8eMmœ63D!]7wh:nmUpbXAmY>)▤=_eKbѓJbU7omӳ#U' endstream endobj 256 0 obj << /Type /FontDescriptor /FontName /SYFPBV+CMMI10 /Flags 4 /FontBBox [-32 -250 1048 750] /Ascent 694 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 72 /XHeight 431 /CharSet (/greater/less) /FontFile 255 0 R >> endobj 257 0 obj << /Length1 754 /Length2 601 /Length3 0 /Length 1119 /Filter /FlateDecode >> stream xSU uLOJu+53Rp 44P03RUu.JM,sI,IR04Tp,MW04U002225RUp/,L(Qp)2WpM-LNSM,HZRQZZTeh\ǥrg^Z9D8&UZT tБ @'T*qJB7ܭ4'/1d<80s3s**s JKR|SRЕB盚Y.Y옗khg`l ,vˬHM ,IPHK)N楠;|`{8z:jC,WRY`P "P*ʬP6300*B+2׼̼t#S3ĢJ.` L 2RR+R+./jQMBZ~(Z OJL.I,qaz)ZRA hi-ҢԼpz Ԋd[ok[Y*V}Ο'־~ bG̔`y%K^-|xE dE[${z,^k nW6wMpa،9=թsr y)/~V$-%)+2W}~Yӎmߝ ^֥~1͎;K'\ie/j>s}\'.\GqS_e/BuLE jjXcO74SS;ğ2=Kekѝo~s`qa];Dn?>7jG%*M97?wR\j_9lj伲^c <yDz/(3ҷmf{{5қ`+> endobj 259 0 obj << /Length1 775 /Length2 691 /Length3 0 /Length 1229 /Filter /FlateDecode >> stream xڭkPWpQP{ %BBBiH ٓda6L(JKJP@PRx":T8ſ9{ߺH}(IA8H ncRPNc$NN@DU0 DY )Lg$b 0QrZ 5LRRAb @!a!@1 #X\P$p굯dH(@z% PdqI.Ȑ 8-טڛBzKk0h4@Bmgآ 5Zc 1!A`Ft1P:$(Lz ܈MaQ13SbAޘț=vC#^}:0BA (cZ `0 0C4s0ɤ%ILCExI$[\#+&%ʼnym껇؏j.nsv4c_v*SQNW4Lan21Sm5dp'x}iS^ؾ<+6x׎?{Zi]gIm tM endstream endobj 260 0 obj << /Type /FontDescriptor /FontName /IUEMPT+CMSY5 /Flags 4 /FontBBox [21 -944 1448 791] /Ascent 750 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 101 /XHeight 431 /CharSet (/ceilingleft/floorright) /FontFile 259 0 R >> endobj 261 0 obj << /Length1 1606 /Length2 11768 /Length3 0 /Length 12590 /Filter /FlateDecode >> stream xڭtctݖu*FVVvŶybɉmbTŶ+m6*~xx\s$URe1s0~q32LElNDJJ1l`/n 4fq)g+"%@dea Шh3W ?#',T?.@[G;=}P-s+[ @LQI[ZA@#Aƶ%g[+S) H 0wlaLͬj͉K ` prZ 1 ;+'dl~`eojlw߄Avw0%') x${w)XXrb4SZ̍mg_ k,,S2k<fRѠ,wjy2upx2?sq8Y\CaXiAVnYXn?Z#ao`זMAw= n@SS ijܬ1qݞ.V`Ǣ:\JN԰-2` fǗ=.[dE79mw.:U+~Arکf&'A3,D+֏%#iJ76z꼓S_}Ã0{(\"f 騗y|>e\ c [/p9R$VEtP u>yt'LwŷכST`I&\:˜ e|4_v ΎF=ͿU&*5p𒞨qNc%/BL72.5ǵ_%Ȳ92Wvq`P^N֑ks_DO2< Hw NrhH98\PU˗W/̎7Yn<sjM&"U-HEg4#tb]}QABxK2L& Pr.ڑEVFެs GGVA GJ+V' roIq[)C<\Tn'=z^ҵ=T Hv1 y \DUlh)Wˆs{$ X^J~!mO;Bd{* W)CP&vI]/88ՋU*~O-M<[ib2 WEhh0eAXy𫿥8/VA[9Uxp=>Cxo6S*vR*EŔzRm+ÆkBצs-!8@*#2'("g䍥mJy̾fl=[ܣ(l0Ԛs~}e0]\e?ύ謦{OQJ8WM& QSx~)UJƍmNZJhn'xmj_yGܨbn 6fPF2Em-6}jcuW@P`Ǝ&BM3- tvhWs_,iS{Cl5R4i-g"΄8l Xo ~bvs!aJE8ؗ u +bF_#t36Ô7CbB Z&^^G+Gt ͘߼ \I]O 5#]Y~K0-WFEtJuY[g6)3OFf;I,Ϳä0\ ER]HۍM8])mX1f, (Wklp~-g_<#|na>}JS\N%`9мMJE~KLWqT^ZlE;R ?=d )d2 M"[*qCqvu,-\F!8Lol٭g:fFV2FUiS4yr-nΧ$ķÉs֛\tVNP8w_$T}s ; n0;Ǫ }6sG'ڸ3;0 4ăyV4+zgki4beƯ𱤶omQuY])uuPg o  $qP >ke$!ЍH"s9l' qbNO8ҁ@Ty%V g0B;'oW-뿍Ƭ  #Wz<:Gn}N=ۜwRR%Hp BƒĹC s7t p 9 qǼ23G'{[P |PCr[UZm.a &yup!D_Wnͨh4Ym|Z`7hdYTQk}" (A7w$K;D3 /ɏ#8LzUr9i8jy k!oI/*#;H:"SXMq['&|,gӢBJrϏ%Ԉ}DjbOIH=PN _kƂ X{l#hȢMXvS<^7 @B[8ӝ=|j.-MD]dX9ǫػn|aW$+g#%5ۮ ʐ`.<<ɰd`o[L0,r@a+p iH,h*fܴQFR߇T߾uo|_gS;MRt}R$$o5ſܳn`GtnNfM*5}lL Ubf'GKr4@n~*bdWȤ*\H8CMJ;w6-%(e5& Zq^Bm,!u*i\w;lg+erWB ii-+@k㸵ӌ+9'h<)H`P>p iv85R"TߤP#eP| \dșrg,,/#4MlE l}Va%2 6?H=] ^o`擾ݩC}mY-Ero>SgJNӘ]/E鎌37CdX~)"=a剭ptpo*ТԒD#֣/sNfÆ: OcEа/Z59dps45PITkeP;rge|tB ))@kA/u8 Q)-Q~@5RF%=0%FVZI[#zdDҩICs>]w,ٺq5P746Uv!Aimq$½D^/M^W>2ʉA@B)WF*dö{*BhR4ΨOd!!*p9G4]j{=i7><ڀDQoHJɩj/I]MtnܥU;tiJİi 2A#׸&0b ohѰNltOc{Vg58H ՞61jGDK{իIrڌvL>ZNGLh=!#$!?*eY-,B@_`ԦK8{9. ?P/d^s6}QfS{zϔ嵲suYc 7o}T䨓5L\4 0Kwu-* v-hNz?◳`Ԕ3g#K9} HA: ;(sZX!PbU45n :OjdS)4g?<J Zirl om֩i@b{@UռCc/!.Hѱu/uc莢Kʴ=spi$qEzv,&/R:oKkB`|S}0*9cUt[ubt̝x`ԯgwO rZG4D\EZ,gR3%<cp6#g#ˊI-$G3RKlkET:=NX3r<+d TPԉ EPk 9x}uuső۞tn"v`0]Jd\T)^ܧ'BD8$A!0Ee5h}Z?I0TB|BqpmB][N, QM0/֜‘;d'zXM>sWGMxޝn)SEA`fF ٻHG㶇5jkmٻ8XŃv1hp@)5U Utft?…_͈2^jQnfH<"7a`X x;PǒkPV 4gQ ||liH_ϱ{S . #_{B>mXdQ|oXrVd_l"Bֵh[xunCVFgGCdgݍakXupNS&)H 3,gkZ`94*,j+9`b w㙉7](P b7D^[3ܻE<]Cfa[Ju4DwYW^Ӗ{OJ¡`?`, Q0naCDŽ>1r-gLݗk_. 5оJ;y*DGRD)z 5C!e?V\{ 1=ĵ޴/1Khl9.髈^b'XηF9ӱ4eKHpǨw* w}RRsf 3rF(bۏQ4+i-CSm'R!Be8/.75Y PDؿ+Y؏=oN}?de!MeN^'KͅpgT8 Y2[6W՘|nsv7ZR`xճ ?zP;yƟNU#`D$9P q>D!|H"hw8+)fp![]1`pA%dw3Omt]BͳWg$WEAvKΒU PCWrgN]ngʕ萕}%w[V1-ݡt^!O\GY]tۯ0kd<0 qNdzT &YRDS bS!`/uc qH 62%yɯAȹ%-La^| ewlW]$I >-kLwpe-] ᔰw$?OVjAQ ؎]y͚G보n>1LT[|:-vg) H`:5:} x[`19fѮ/jzH bl^gK.r}Rqs>\ߓ0akZ&SO'^ß Dua'.ٓxkhq]:fk>}@Rq@%ž=qZLљf?@JK4_YФ㤬Xo< jWll*,x {VW˟ F{J*LǙ3Pbb'p~C0ɽ Q>G"ׅD;}<9j ͮS1z}딇"{eHviۃ\]!}yYQ֜).a{y{y9+3*)eX%,;%ApU~Yy—&lnGq~UYa狌m1%K}̇nTn}pEE ^(li(8Cꑌq^i,)žيr&5*Nsv\D8i`O}i[e([ÊbkdXz04bxY( '^,!ݱ{ר KwJy4x 0F\QG^߄Cuns2bYmw]g5BvXtI9MdrZ 3Uxe}i(rG3&7gL~XZPl{m̺MLLϜ2Y>ofEO9Ywo叾elPKeMXa03ѢAJZ8ėYQ8@ b)fMbE #hJx9Q)Kt_0cv]EEDfm8 {}G0"|\)1Rjolu\q/v7K2FHM/̿ >0sb(ify 3S '\P,iQ #ZHhAfmESu 3XܝTjϨJ^4ՀXKcQ Рl,Щ'ގjbbZAeQ~[M/xr$6<Y$ij8;§)I jg>Mf[[Xj쪛WhҮڋ46xyu!RIvA㧧xI(㒊ׅSiPSɺ:"gu.i'gML-ՒFr7[oW۟wjO2' 6i. _]Ʃ s*-8~!'ju ߍ& qU;Cog Mfffg:jn>CvN O,rf^8Jm%X3kwÀ!F,SձX+|/I>HnS&~w"{AAK;nd|d3߈L`ׯ[h^ x5bPpއ0jlvdTGq5Ga T@[6'OmIxށn)AwR^R3!(G+֌C=Xx8U ҔГe;gϞPi%A^A-'DԔ!gl8PBG݃'_s]TLJ?\"Wk<:ĝj^!Rs\>Cbjv)¼Rq@ $I&ki# S&fƓ0i;O>&U\x:[Fc ∯~9=K{tcb*V$:pRn7u^k 4K{ /@#IoC.>yI\(SpVƏ)_:GlqzvݯGpcV)5nmezf(zr|vĝKr(L")Cn1jk ge%ٷa3|?^hnU @o_ڥ PFmRC֩A"d ^Yo+9"T?=~0F`lt`2Ä5=WvVmVd,@X%=@:WaQOw5C6#p7 ņx'eBAz+eYZU`9|p#ƷsSXH>v^Nx8Z0WA30}T9ؿ`/Gֱ3Bp}SlB-|lh!*L k:qiT' *(첏L+Ⱥ]/:}txK&^ryqap'/ڥWa@O3oy| `SP>qZq )Q&:E 1/=ّICq0}~٧q ,{ pEqo8UlÛ{ŎUfVPLw& UB%l˞Z 9zH B"ş*ψaB E?zT]UM}zm_װxA8,>0-1 8=}؏yu7q>_}+ҷM˙=DȀ,i endstream endobj 262 0 obj << /Type /FontDescriptor /FontName /HRAHZV+NimbusMonL-Bold /Flags 4 /FontBBox [-43 -278 681 871] /Ascent 624 /CapHeight 552 /Descent -126 /ItalicAngle 0 /StemV 101 /XHeight 439 /CharSet (/B/C/D/E/F/L/M/O/P/R/S/U/Z/a/b/c/d/e/f/g/h/i/k/l/m/n/o/p/period/q/r/s/t/u/v/x/y/z) /FontFile 261 0 R >> endobj 263 0 obj << /Length1 1612 /Length2 18453 /Length3 0 /Length 19297 /Filter /FlateDecode >> stream xڬct]-'۸m[۶ѱmc۶t|y߽{ZUfͪYkENL'dbgucgY8)\l"@Cg ;[QCg 7@hL\\\;{G 3sg: Y yOߝNf\v6@[@9`ja +hJI(%T@[5@ ca uRL^lM,)͉/d4 n E :X89X8 m`aklbvSwaL7y::`g7埒 lhap;0p6/ſh8YؚZ#/?:[m hmJ7f vs:A _&v),ߔ;DoEyoyOhqkk9C c _6 7)gÿ5+#=㿍N@ gcsNˮjktU_112Oֳ5OEu5E 1 SW{gG)v&k; cfM/Z:;Z_+53gV mM26vqtN߂_aWy-2ӝr&D{BKU k~pU׆7Mq{,>Ŵ!+@AsȠW~u Ψv;WE0sLOZNdkхZWxv=[ȾRlkyAvLB73r`ؕt+~W&f#iou3!b%a =]C^5֜A6/{lć-m?]sϛ'cE 6a/ooW Ҝd3A&gofω27\=pdhOQ?&iz__Y*^_v?Rẅ́}Iq ~gW"dj~EJ "G*|]J}f~lrH҉W>gm9Lj7Iӟ< r} [!#n.g|@Dy[$i9s+X‰yD hÅ)}_#=$[% cPVkJt4SqF?zKީ&A>XkSJ(Ce+!J/BRϹ+w|(A#J0FD3fF-lK=׳}(J]dӶlKc|R/1',l=&^Eu4@2_sy Yf7~a]f!dIM&щ[Pa.bwJ,*#mK2C}etbtӚ֧iZo]9Kgt?hCrnPng5&Jeo/tn 2|4uZU<{ Eb7ܚ8[~v5͡㤮kfd]))?B,ǃ֧ÉN:x@ܻwj+YP_K"!:>@:b4ήftl5l^olؐT|3MY-tbstUqu}Ls *A]+ I? ɲS9ʏ~E5g5XZ`&wXfDTrgtyIi KIR])_5x ݏQPB%EQ&ɰULJ@DzV=&x1j! G5(8)5#GdO2K|ZBUf^ۭuEr'UARL3__wRY1vB 9 6CKit*tM0qcl3M"D] Q+# ݐssOЀreF710Mv NS²['s֣"F]dqB۪;/$[oK-cvRږ/8*T7Oy➨$%H K @rYUDށ럤Mkgw{_)̕ho #7ǒG%2\5u.(T<ݖT.B<2UN~Xl̽Ist؋A&'@kà f^ T33&zE2]lN;*DLx`W yN̞OKhnͅ\muE`0CݷCg5XA @" s Z`r|^ &VCuMe#҄?5GwS_Оd,YgF1ȥ| ! SƇu"ܷfJL`qТv|VY{.*DCw~7Ҙb-_/H:D>|1 պ"y:o AXI,Pɯ AO=o $sc _849y$R, _L/+O֒|NyTh؃kl-L mtz')g#X6F(;p!(tlO9[~DqsGĒ}υ*f ONv{ -:7wAVsM\1vFuo0CUun<õ/?h*(¸){ ԑ1e92MXP~VXl坷DCط[FY6˜lWFb'lgԮp,2l$p][8Xϒ/ByuuM{7l%NbR,m4fWD糞_#6JX8BtE Sߍqq I?G9dlzW >Pm͕Gn={IuܮB^^P:ʉ8KCe *Z27ýXcUfGm>z@&"sTD:R%q)[ u}xb:Ē[☳r~1ї6yATF|45Gy3} {Cl<\SdnbE{9Ύ 7F'KEn5$+јXqˇe6y[bBbo_t#Hؤeְ#1\X(2;^cSliT\Zd&L|_|1L+#ٸAY!|; XEJt~DaArUCoa _Mh(4#%H@W6~e.ng?,@k4႘83wrߣeݶܱIEzp ,'xoռhgiYskO(dA1LMA{Ŗq ;fHm^ؚ4[mtQ_T.>|wDhH_Yצ!ӉﬨpfDo^H@uGʱwrVuc0\^J y& T~ov/K8 fTw}* Q)VV=Jl_dr^Y ?Px2ØK -GMDεTI[U&؋t7hq]ڌ2v '$gVؠ:etȜJ dhU1W?¬ xA-یK%Id  H9z8> 4ajOR ˆ`/[Zy|A. I'2f Qi"=U\VV MCilT3Gojc[sE^+sGY 5>i$xz;3 , ޅpP1']b',dѥ<~Q=aJr!R1%5=G&pOk ^DF7Opa9+_O)dG['s"tϪX W6/^}{{ERWAvn[  ,%:h5#qI^ޥQȍ-q+ˋB(t CЪ ('S]^9ӯQ QV,y._O Ⱥ2XV19.O -I*.\imO-煬޾:$Is-y)be)P!QS|Q~:r0wġu)s~mepʂ/r@eRacqA"+E [cu)vTT/N8d.0(nlVW*\L`8۳Ѝ=͕ ݇klG^:[$y EB_^GUb5!Z0M߰n M3A/)+1H=//у6$~H7C##s\\mz\C촹Pգ爾?+gK XE y-Ay}n8 o[BԾ;pD&>-;>>(,+m=%`'Hz@je,11.&o0.XnTj}# Bcณ6Lq7.-y[`XP |?=c1w&|!p M4osjYy޿laD/Ff`7DH2]Plx = [FFv}L)yN".NSbTZpO̾06s) |Qv^kLBuǬзYpdg?Y^b"Bsw5rkP=)V^5LC/8'\cKnכa&#]j>|'3QZN::FSm:)[Ӥ$U ?a$cDBl?Ys#H8=w҂Ȱޑ^{D~K <*]dWD"l}nY%*Zk#;p;,J r&[rPam~$bȉES|M<1Xo! _epD#j(hE7r"wB-΅5I}qe;il5a'9DƷ7~!O_[wn~)IʀN]/D]~(ɜ:p4%42}ZccKj5)/LޫDzٳx^~@|BqVD?񆶺(^Cobܠ)e=)Twnb:Hxjs}Йఓ{\evV:JVfSDa@ B%02cx_s1XQcE16)q;^d=pfo"2G8+^ p!vȡ䥚r( -0S^p g95i"BQ7*h<&{: Pmgy7sWNT7$G0Cb@Q:.^ݚl""τ/ Mv:X*@BFr8s @]`͙OEeZfRWzp~ %㋛%6G^eqPw(gciж @N~g&PjMx\:sd* #&8yLHz-'\^72XD|/2њb3,VUȳG(u֕Ghs aW믣+}\)iyFքT[r}7(Q'Ci8[bkAQI0:ݯNIݬjU2<̀DqQ4{4ԟ< &uŏ'Kh!?D(,ʏӮI".KJKbw|jiuSǠ+[;ӂ,EqnGd?TLȃ)Z[v7pisű=K1q~L7 )(=k#e}ՆzNJzN[ u1ѫh!jS~*(S 2f̱Q%bѷnt7 e|#^~G ȔdJEex ^uע{ѷ^Z\E[y&- )ybGClYShfv*8Yj)RB  ۴AOK\: Ai|M9֯ 3EwM06[(d&L5`b!15%_ޕɎG -fz5eR'2+Gr}FYuL2-+pGř8u{C(?&hg.v0@hl5?Ml4VQW*6c~!.%+MB dž{;KD37#~׉ gA4K &m Ӓ"YPL*#D"MiHdnoq:a 7| FRkɌˇV/nO2}PbL̻'3ne+yј{Ƀ]6Q=1< m/I16Yvvr"TTy>+ 1P)śjoӅDFT+Vx9"NmOWBf"3:%M a/vAqx_ r{rY9ZwL|Zt{$18ҵr"Z871:弁X LZcfHŘGi@ѝgy){(X a66[avxJ«OT_`j7 MM^WsՠGvs1>O>*FVUg|@\ls9lU5xnڱ 5NzGy FBԛT;(Io[zg=f_OH]:MxBs]PrAIȋ);$帯~?L$,um 0 ˉ2W`u$E}v20F<̅>rٜSV77=٨_UfTyykF$=wds!i]\jzgV~K[ #¦wdIp5=-kmXeOfȍu 6n5ۮ?szM(ήB`;gQ U\ѨE~ڪ]n7i}7xs3%,@ ]y')y@.B _H˕VJz}ﷅ$[(Fna͂ʟZU򞴻TKn-m%uAShݷ*V4;\'+.?Ɯ-K0 0aúal_+V+A2U :(X& [I?nP$vR5$iΎs: 2at,ڨӔZٻu"?ۜr&Ay38y10$$((&V*Fz!tuERQv.Hޓlb#" Rº_͌WO=fH]#nx|-klCB#XPI8TxمmhGKhA!$&gF Wa^O5;0{GFq#]#5&uُmp\T'kCA9" SE}c81C̜bP,,%)O:S|ƉFN ޒr4=!=fI!9nb`S6Qb7h7SFpU0w?CDq]0!zb^N,?%+4jvOW>&fU%B7V$s5`$bǬ%sW 7uqHs. IX-h<π >i1HSXei !<ߊg,Gp\ ?Ry~ԈW0hg/܅+)=>D$=-L[. 'wIS( J&w<4j ҿQ@l[6nÞ>6,aC(agU.wltW fzאZOT{ЍqS) : !&9ي' L/spl4QE@h=Aɼj"EtBR|OK'g7ˮ'ݔZiCi,hzt&x3g#Uڼn˱[Uܷh!v- bKީ:$ElVA0lYKqp=7g'UcjlZ^-'&ݐ4v=;ǣI0 n#p'ݷQ⮦w/UR7n)7׽+Fhmwm)WJxp'C+GZ!vB1w 1n,M:'?># AZL\ lJ=IW~qH=hpcJ*{w+˾֯crc~>As,N kp?`T=4Ha2be'yh*}aMB.tYڊ;4il#ȔLUi4NFsj8znrfO]L;P:],W[oii5ycWEoTBrF, gkH|Wm ;j&=Ԁ䫍+%m)>,9h_ؙCvVUۚ k=U4='u#>+ε,.&dY=4ƣb:]bNF߉~p5OPKdSnZ1E[-1 %a7ۂе9ݯxĿU:]޿qDBov;b7نG^˗d0/;3I^Bdv ]6G c}V&"?~ eޣ j| h MwU}$"EF*+5l~(QOn7:ϋwtσ= hhF #e7N ]cRKXꡊ `VATy]tM??W@IHer/X!sF[d@QtȒYe5ē0E|V̽fKؾ֖|xGw0^5Fd%uP5V0~>X~;B:OaM\0߉)iU#$Bx>-k+ %]QJә#»=+j ^ye^HqbÌM3bv0fEEQ:O(\X΂Kq~Qs]qs g;hWA-݈ ?3>^q[c,W黣MWhxaAm3fm. 5(1!S4\v~$&&=3ӌqصG+QMxR<̶;" +M- T"K$ .VF5W)ɪ;"4Ċl5;Hy&۬qFb]}4n D*59ml\\'Ԍ(n{$(ro)R6xU n\<=ʍPSq06_1W;N+)K nE #Y[nOf)6|unbd Z)=v_G]A>3 RW55.6=!L@ _F,hM)H߾Ni+jIexgKۢSej[y8L[GMuu/w[o3JQBl)FII%SCXV< ,d{ s@l2sÅg"w"v#5!GItOTH^a \#*[XEⵠc=w%p*TH@9o96%ŀdB}OYcq^4.**2ܓMYHTR'UD;}ʔ Y8{hˌV "ƥPVz:[e'=Im x~NF{`5V{qSO`qU M;L0H%ސf zqRu*@@T&]+x.BjNC@ 6ㆷSsd5{ݸ͚ sL%>D"jE؆e#v~J^՜9h˔A QQkƪ}vGKfĆ Dv#ɢ0hR |CCo=Z (g9;w X%8v,!IOa_m[Ts+ca 9D* ?_É:rQ&+~ 2džD%luq G ֌>bc<a_;`^Zm>~2?\4Or~P~t "8d'Pr9Ye9ip13ڼ2b)d nr2-[bb҅v= =Tn.&[Vk1߆IJ'Rf@4r F Gv%A kKYl1h<Ċ҇LGRѯ9a,^t}F-lAI}o'+BŇ<=L%arќ-뀏&6=F(_`&v_7@s1BvA%1KHQMh"TrYrL'nAOe_k(YgQߪ6j>݋0vZMG Զ .Y>({WZ`.szCf`FLM:;hONS ~ k& Ʌz5ؐ́\sIp3&<~ #M-cB} kywq,2= cXK@S2\E~1湙tB$3 9*ǤUϿj[6Qi8MoFu[Hm1 7%}(/s\=o(tEWydÄQՅ!@9*#nF HS:o"%}hft/:\rt"|j?A"=a"r,a\L tAI3 $F,"46EˊƩ@0|{hN:+?eIdz65De.,jyy"9{YZJr=r7Y.MT8$͝+ LȟXJd=~.LPl ŀueԑ 58K0tV=YQHQ&k;_~gBp|zڊA)ej3F0<$KnK-7űtT(5\)pћwښ-rNKPjA?^^ 1g^䒒4d#*qhs"[ O:k T"guOLEK"J.U(V{ +=~G btf_ᛥ7S Aّ^ 429p)?tqSjG^X ta02b,ZӴL~l酏N+ޖyѽ̩TSwAH?֩M|T.tٖx"6s&8kŧbȅT*v-xo+Э#M yYR,27r%k-RʍУ{A g K퇘EҼjebFF:fE/AbB+g"lEF:Ff[ka Cc^L "tlI ᝜S}/Em^yc:$6AfO=O7߃sab83׏O:xSHz冟5vۿ8¨)S#|&,X=6UÐǦF5McVckD#)1P1ФZXnLoA|6BJ<{=FS2##Ӧ1MV _IW5޼fuR;ѻU[QQtCujSP`+˫V&" ,TI '$/{U*j#Gͻ+`ұ%6E⡼L.W.s:Dt #;=kIQO_Dm\ ȻDY٣IyxƟ.P*FRp!F/im/{Iꡱ=Q^!kt3=*+iCxB<.p\Sdc7vTΆь~wd'u‚,ř` \+rI¯ (PkJGd(d>EƊoE꟢r̛p.r_RHV`ӋЌ7-Ч"s kno2ҞY}NQ_:x́dލ"l#Ϋ~Sr8#COnߣ sȂ"8(c1=\0[\v%}y҉H19_s;DjZnA^A2A̙eϏDn|^?>vG#$|>UJgm)oVg'$I-/]6i: L_x0\wrLZ89?`@2X2R&%2ǗCv7i}DmN҈"زBI*hŀR7C/T6W iIWS7Hf.(SH:CYj_vgxƸQ@}|&Ւ8#THP 4K!~!O}=,MX$:~Uw:\IeBkXa`5x{μD"j0q\$7=.Tm +M)FQ'ŏ`\:r\m{I^O+^~1oP,4N_X4z3$ĖXBoPǶhp[ zV|9U&z}:}+Bg,d_ָNVOd@N E)Ik(kh`p-hz͕\()OH ixi: I -V]ZI5 pX@ӟ6t*_L͂)aknTK a3$s d{|@8GS"' ghC50LoVZ"`v-jSQ5V~a3%DO2xMt*HY.w9I!M Jq-惆F\,u\mQ_vO1ރ8+j.qcz85ybxLcoG$ptt@#^͚b'BIDF|Gq8?v~e=dU] ]X;ƊӲўQ`Wf[n; F`ܪHoqBڋ(km P m#h4BbFpT>BFw=^6kSbF253"ow8_ " {bU}AuW'Ds C3ƴ|T`{0AmY"woz`[X]Nv)F'*x-s0qz !to_+dTNkTOyy Ɉ8Oy%׼O2C}W" FJt!>rl _`lQ LUQg5 _#߄YORZxE\A\W.0V%xt,oj:i$@"++a{TJjUG(S/-0"I؞ \zF 3)A2}dWێ; SԾWI \WäE dõ\`&d{ek1Ux_'V*lBĢ2 yH x|(@.PW9}K_ii*o "H#fUGAn`v̼(X 46;eCӓM%C6ND{auotEA(K#LNzT$ۭKE]0ZtJm6`̝}߀I"/ Ap(9 W BЯ/G?vJ!sYζsT\z>~PkCj>n~[w>ty7Iḩ mHlZiAǮҚ^qw,!a1!wkx755QQy'-r5qo7rMoцA_3D#dA|4lg, {^?RQs/Ϣd$OCp _}!_> endobj 265 0 obj << /Length1 1166 /Length2 8243 /Length3 0 /Length 9013 /Filter /FlateDecode >> stream xuweTۺmwNw{54=k [p ww essߨ֜_}2׬)TԙE- f ) 3 7suV7uP`VY^@.Sdjj B 2uC$L]^x kW)\l/k _(?@ xT@. ᅒڃ\] 53jrXtUGO(@M70HVG i?bU,M%-. vqqgeu4`,Ζ, VF%,!$pFC3 0d2'?uu;xl vs$ WGVM+HV_ 1+ @N5%5<Al>ގG3l z!{;.PWK `6w^/0_{ES(d? _`w=-.%&yI `fzqKF>.f'b >u5΋:x@_ 6 ⚗iOcݟKؙB+l<@mп#bj6uL`g)Bbn/ş!H M0qӰ:_O `񏒒 @ŕPP<˳-/ @ sy@͗Qbw=ܚ7ϮvY`lK9sy?IX W*¾#q`y #AtR'ďzH7y!"H9(ѥV9)#W")?(#}u6N4Tv`q͆J#|93#-:`]fP3%aԗl\mZjgBH+Fƭ`l7 V RB:QD51vi[Uͩܐȹr$n)jvEZO9Xmjp6jv\hoEZ}V:=<*Xy]go ES4 3ݠ:rK\5)7OnvZ#Y':ۣf{34?Xb,hڪ1޾ᬍe{27{#|P Әl!z3Kw:#w/YN0e]j`YjYXu~@xQ ;'xr/G'fiW2ՋZ ^tif"ҸO`oa뎹 K vfL>"K].26^! X! Yk|a/?WU:cUV+[Rҝ46wڭ|uJ)ieb^Xi-"4HIKv`/lk%oڋD4@q?킫>Je uFa^-V{ O[?~R߿VbukaUA)vU8l<#,&Ʋ,,44!I ߧĝՙﻕ!;vw$5.CHB fF}Q_ӺFGRCb'E0X>X㡟4;Q}\|T1Ս2@gZIm?[*4oE @+([ĩ%hxi YȬ"u]L"1T 0ta" ڻbOf msQ9]ToVLܽb~`P@ivXܶҾc:ߤPNz4Uri.G2E pZ4؅hr߆Z:|ՁƠ}4Ϣ(>4]XhX;և(Mz{֓?ıo!W`7.Y}gf\t0NQmy&D{l 肴ߓ=ڥ07j7Hp> wLHqB཯tY`8nX $(zl U}))Xw4&[Ki˿RmMHpRS1jTaL6 =[xV k2XaXҶx\:͔}WhTY6o$Yz1KE:Ҹ4(%‘z 64ps2aw[ZF0 MT}jFoqXz{t rtDs^y?!CAq:Վ jeD9"Sފm1Ge4ZVI_2 oJ %Xr݈dN/b;<=# ]Q5"NҸ-GkM:#Bs_61ڦd䬛8DۋHrd%NrĘ`[^3f~wǓ3n:Ee j$VTiVK5F?Bő_ IYW'@sIpRNs5F[G@G8# .ԧnTNVbh@–)VD]& eJ,5-t!S~8|A>U^-[?8e:)Q/|DՔhZ1/({R,Jgw D%mcvJj&wu0rFJm<nUpB)bΔ;Ӟdۗ/eLXeX-\ya3G>+ ~SRfRyɼ1[1XOKL 'ڪZ'):/" 'V?R`#E2Qh °*Zc%%J |LF\tMW K&N\˖矩f[,'o yqΒ!("ήpߪ|G^ ⠜⹐f᫰bĹU,AShCn1HkzIU|<cvPDE MO@İ|7ϫ+ Sf;){s.?sM ai4nt5",t35u]::Bh@*i{``qQJb%&;kF8WN/M>8̝dM&L΋id iZO>w۵|[o1D}QpROqҳ&TOB&3|⃐ ygfqر_z681f̀oL|Q쿯d+L_ZnpUhU֚S~\W#*MFK9dQh-u&h),wihfZ k4\LYnEO:4r(J }%|wv"k>gP{q d'r1{:U ;(Bo}د(Om*v`9$vLLkwMpm2Dr-A8X:LLXm(ɫQ԰pfT#Sޫoe"4)rK\Y,SS^y?WzW@"k>MΟx\S6 ӢsdE'Ĕ#rπ,#\c徒a+Ͻ=àݪڋlw<%#\(ubD, Ilƻ˙65%ZtvpA %%cdniTl$eg}d!bxsda'BJy#U N`Lu PUB ïm\F%-gm8)d5HlnAua~Iik8%SLFyEC i[m*Cxt_u޷)Yt3H I(X%nF*\Z5Е椆un1_1_;eO گ 1}JF%&r~) N/Љmxk!@YLf'v؆J.un[2iyP=u+1faTqJ}탑, =W&6AP[[Eo{I>96ND TO:L+B21h:~#KiC}T`m]cqe;֍V[2[N]Y&XNotcfF2fa?5v"U8hD/ry\915i{D!iq~kug*C52x,ja/~C`W"(}!ՊDr' Ns޳qҸE t]#A3~3YUo8c\}9ɹy.|.\eefs2b$rdz;Xu?c ^z$Rx#_T9aEb0b&L:o;iv\NS˱.IeJ;.$yX%%4fVOOQ&AAYf$+ 8?g+XWpJ9Y'}'X K`)ޱ- 3ji#XXڀF:𓡐3\(1]ƈ{C Yz| O0'劶 6>J )Zv s yلGykdxM5,CQC Nwyco\yU6Mi3(9E}Bү5ldj c^ EN]̾A_U<2 7DpUII-Y;)ѓڿXM׳cO:n@|jEh1GGRtbI0w{[,J龙۞ nl/G@9C\1\&ej4TUFr(hQ9l^ڪlS>Zgr^4(>.?eHCnFiGh jּR\cI-"I-D[RF '+-U]{7yi}?:}hixډY0 z.1Yң/@jMSy&Ċ$B Ήd@'S$3`/ IAЙ;>i:J+svslMIb%)^ io/ʅcUk sAESG/CFPn!6𸲷ӄWOє>D15-]B%ȠgM풹#Gx0ɸcQDKqc$}S ^yxIWBۜЫ-؏ŕܩÎ}? _ax+^_ -L/)61.-HI9X63#VAݩhkH]8|U^%A#"83 '#,ˏ3{JwHSK עx_AL L(hML"3rv.YSzvv|0p^Zx @=Vc`X,>>s1+h$>: Nʗu.A(N ]9d핓 Ǡ_vV2n:L_~7=>O38yTH}SG7W np[H70sN?KMZtl3`#xpzS˥ubptעGᄦ:WD *2Yַ[*pq)1v쑀6-s miVv)<ԫaщǷM0)8VػM18hlQmҳZkp׷]+dԷw wR)uߚ(gZgvL?c!N|/]2ZLdy.ݶX[F{K[L`e#!K\!$AVmAYU6[;uH)l͚> sp]цkZW̯+0$ѭYmn'1ӗ_۸>cll>sL5NO.?bfu;p "H%X !/w8ǫI* %M6y=Ӛ1Suظt2'`Z dpNe%8v8ZGwj=IdVSsϨ=0&,J'ΉL\S4Iی~Zy|88 F!\Y!^}+m;A+UJbF\;¡ x~Kv,A֥ëYk.NG9i}mf['$XYhp/,^mD*RF '[`5D}3ٗiC }ă3i嶊 6# B/Skp] xv@5#FR[up;(XT=^tɚ^MBP6d'JǟdON.!f>bfm쏈1rV%ܘ,W,4V:a_6~vsW*eaȲ.NTd oC4=Ȃ(qkJ:dnKa#•td_*prJW\ ((SuhD;؏5˚{E(6]WjP4~U -pt-!uE ؃[a&|ns4^~3;A߄{*WD^Qًmq)HfqRa8d:p @ѫ`S#[| 0\5 n9Hy 9e1VeƇw{A&76hm}ޠKP6x-XItf4̣֗updg]?עL>կgs{ߎ qg_IFJ sgNC&Р>Ip+7V7%ažⲏ1$I b/#S/aFeEӭzn)ܰ %4,d^kO[UwP͍T0dDi̋h<+kt{6QVJСFDzC5z<A@gVÝ]J<LT'_L.k endstream endobj 266 0 obj << /Type /FontDescriptor /FontName /QRWWCH+NimbusSanL-Regu /Flags 4 /FontBBox [-174 -285 1001 953] /Ascent 712 /CapHeight 712 /Descent -213 /ItalicAngle 0 /StemV 85 /XHeight 523 /CharSet (/A/B/C/D/E/F/I/L/M/O/P/R/S/T/U/V/W/Z/a/b/c/colon/d/e/eight/emdash/f/fi/five/four/g/h/hyphen/i/j/k/l/m/n/nine/o/one/p/period/r/s/seven/six/slash/t/three/two/u/v/w/x/y/z/zero) /FontFile 265 0 R >> endobj 267 0 obj << /Length1 1199 /Length2 3254 /Length3 0 /Length 4016 /Filter /FlateDecode >> stream xmSy}ss"Ѳ8O"Rer05)ÉȢC$.n?A] ID= cJ`(`yS)gOGE =H`$@(<鑰4HEd/ K"Q.T0e)%--WUU;DzޅNa pܮ/s Rd5(69/g9"@Jы=EĀ?Bx"{{8jN{#&/ @#`0U XWnj3?21D\`D;c^@ @^oLЀ 8< v\#N7?&* `p0ldO.D$r7x`(JW JO멣C•U*`8ާ*Bww+V/J#3 %?(^]C$* !7qq~ϡO#+!  xW*(-D0b]Oq($yw8= 3sc݉}(!-)"]h*}E1ܿ]KP:}=O/|,hxUs kڨ}ߣ@|MDG`|'n*^,Y%%KYadcDЩ8ewUf}<AGoLM{e:R_vvz@J+t!!˫=[hd~}6oϯ78<@0QkZosު$#ɣ2hS.,=823ܶ>Po VF"6t'ti7n dYMa<&TI“c^ Awиe2_:l.}pV oLeY4:ʤB᷺?|4(%FDsĺkꮷ=I$w zVQj|E{!DFJI) , f*@]+gYB-ޘ":/ݰP^Bqf@U 4Sڼl?]~wYI_<&kղ},ҴW.~#'g;%J/φuwzF [xg56=W[}/;VV3'hMN/?bP_ނ[ImlF "9HpޠOJOrz(Z@W~& 0 Ipbu?)L9lA?aA&YM~ngc;[Wt|}y]*zNOҬr!KFZ򤕤j [?|`$ mXWs43HYcW# m~)<x-D'r{ ~sdt[* >ivЙ|{⦹"{"h噍΢1i4al*g-F&p[,]bT{$&IvT>ʴKmHo2/섰_m<^P'\R3ҋ]h^Ȏr~djEgcUlsP9,~ss{NJDٰVTה(6ޯATyIYF,:y'aϽjmt͚gYE!'TxizcZȝl𑦕c+쨗!>{8}1 Ҳ vҞ\(D23.vE]1tsȣ[h:3^e|~#]_к׳[kzY=6ba=+a{X/=Ǜ[ ?o R>rz WۆS;o.| ڤ,R5[8K#Ke ~}Ʉa. YD4%o_`ʇ]ª*KSN'_Hgv $%j' >/ eXp?u؛}g%:pj+Qq4oܠnfre2LvUm$*QrۨHX@NSR)ҽ uup&Sݴ$VwuqnECv)Pap3yk3jQy+ބhL6wg##Bh3JU{z`{2_m1@jG$`9k/|ՋbXM>PHa8Gpj۲Ĺ5/l1+*?zAI[MK[~z.Ի1wa;SSd֭|'{O5(6_^IJ#"WS}DB3^@@[.B/M_9Ju@TZ%*FEeKܜ w%2?qZIu=E3=vz!Vl~MxI_i$wluN@ֶi$s) dGfr>ӵz S\#~֒cw9+rDZd?/u)sGB؜]m|Z*{z+ }|Llcd07ppLU518n%1!S scMĠJ[k"-6;蒮 p5^a3E )YA_Do?Ukp3bY+cQ "DFHn6ʍYsޢ#Rj .,bH*CT^|+zyceX2=FZ ;Ng/Hu|#%CTFF|%/8: ߘ~jӳ|.X d ԷxFTġ+ǦD߫:P\v 99n{"x.Z$\;o[ѡЌbKfr cMQc4~>_W݋+=,GǗNUcW$ IQ~3|W7RWP7 sKG΍4uOqc5y쁱.P)yzʠj{׵@}m|En\9ٵ endstream endobj 268 0 obj << /Type /FontDescriptor /FontName /RCFEMR+NimbusSanL-ReguItal /Flags 4 /FontBBox [-178 -284 1108 953] /Ascent 712 /CapHeight 712 /Descent -213 /ItalicAngle -12 /StemV 88 /XHeight 523 /CharSet (/R/a/e/eight/l/period/s/two/zero) /FontFile 267 0 R >> endobj 269 0 obj << /Length1 1626 /Length2 13716 /Length3 0 /Length 14562 /Filter /FlateDecode >> stream xڭwctenl;vFF;;ضTl۶mƯ;}zOcggsEI(bbk qbdeb P6vvTVcX>b #' [q#'g& 1[;w 3s'&-==?%ShafxqYYl>BUA 9`ja)*i(HhR @lAS[?_92}q@@7d`rptxX8l>zd Z9Cnj7 ; G0%[G'G#?p:9C 54:Uߺ0Z'# G\ G`vpv1' #_gV޶[ 'G)+GNGn3 fE\@7毙adbkc0"0+:}XfoB?rv󿆖tR0ǒ1|ElY[XZV&q2h-,L,Z8JZL,S#~-W19XY؀>xFVѩ[E?T ADT58}`RmMWQQ[7'#+}bRgy#' 7G,W?OzFhkب:٘|L R>Tgrl|AiNUu{X! kr*l;}S6yK _*&>5۽ uXQw&γ isרZ N4."[FUGP`t!!%{#ufG~e)51(f7ăbmѓ2BuF@of0n7r؈IQ K i_U:;]qϩmv(nuN1彽_eL'# u6M,mSOSXġ cTf^ji" cZ{,`@fl"p̭/$Cщ_(iy2U:W9:~$$}Ã+h l(ê~/<ݻf{г&0ݪmLT3^c(}T玸CP&?,pz#.FOvT@@VCzN ԦROr5sK{b gO9|SM^_'s m}4fL;݆I:v>n`tN0aWH'uĒj.NS@PucieU&e'LS--["H(y ,n ;$kEdgĐ\58n}IrEM` 4p6}-EC陗ڼ/]6RމL6ԢWn@Z!ɚG2&?ş[ 6Sh*[A1Z}pm ê0IIQ9 fiLҳfsl2m&-Ja&?bkf%gB$.IjlΈVOQSr*.K)'P :Y.Wɬ,a^O~,I- QCZZ)T<^R+gu{ ju}`ZΌӶks8~լMO  FPd1yn O-q.,&:bj}e+=v;x9\a^JUw P*` ͽ`>o'Ԭ.}6'Ia ~w94rK~5Gʏ֊ЌX#i -:*Y\;`TO>_ֵe2ceP@p+ Ɗ| 2Ë&Fdb)evvS`w:6Bx{;@!ZuTh V1oe  ᎄ( b7Y&]}o7}MMwmEUV,9\{ `rHKFdΖVuw=,sp>Xm{|=`@ҨT8q".B3N>#E_Cl2A˅{agIzo".5gUȃ[-]#UT7rY@!F*Z [}slﳅlQEU3UĚY"D"GS r'C)T9:D4enѹ srLjR{E:Ú N ̓y[5?id,4K%;ufϥФgĖb4YQWo Cmg%|sX|90C!! r! /q e} dO3Lʓ$.,.lϐeՇzv {y' /.TZv5p]ⴐ`UM񈳸kvNuixW&kaH7O^Qzx|dz[_V?(JkO6zHQV{/wvy2R^z7f#RU~ e~iX֭%upFo&,sƘ_2LH GStp6'kxݒ> j^(KFeA#p";P <ٽ|mk1t[7tZBEiΰB&gGS-Ikszڣs`>N4emn9݋@TˁY ak;Ʋ7%U藇%$숭~څoVIa? !'V^ 4Y#PcwVY_w %˻YIOD0 '/+m1z2 %rR62Oa!EĵU] & Ab7C4i21̈ó|aS+t1qi OWr;7p*s HXtK Vn1;%Y\wR̝h *%jf^x_C{W@6CZ`Ǹk ɅY@* KT"2i7^A3U{B|T{\Y~r:O}>U^s:5שEtem98@ 5:"/>F5j{|߉I5^h5pjc@ȦM=u*{q4K5qj CͧkCק=vl[>јEhvZJKKuKtCM3HJI7@E=WF2S%%Ů럜j_`.O25ĜpQTѤCe ȆS;Llő8>6b"Jog^j✪[Ex 7MVv?\ݼ"'h]jD .F;X})oϮGX0!-:kR|)"0ӟU3©A FnX-W\l-a&j8+J /t"7rkJ[ci[SNWrio?hWc|y[ O8H jCNH2$eνH/IOinnXzn<62=ϒ%'OdCr"@¡89 f%?JSB:^@i E1\-⏻O(T!H}%Ve/w Y{ⸯ6>o =Jcj*i9fZ^1v_G0$7TX7 -KRr?Jق;>.j 0fI8CKd2rVSwwX:gI4Z+|F7K}_ٛgzGùޘQeY,ݼpW$E]2?c,gQLߍL}IIOf'p P~H[pS"9m s٥p9!T 9a3WPɷo+;^`hskIȓ(t50iEa~[3t+$Zb (i5e 8(Ű1Fo# BldN )gkE)G61Svs \nNw] :lQAx>~ ̙މթlb9t5EDl&0# ʿA}v(oi\:D#l4ŵu6Ws7> b`ZĐCOFSPwhQ\zU:wF*)]p1Uβvaw:%8q|q#gTc9'+ 2-~Lhz\hnWRGdEm} Bn3בnmnրoezXݟ˪B՘MB?qA{YXɅ6T iMV9<t6{m`4ZP怍Β-BRwc[*DХe|'v<|\D zv =\dĔkR罳Au3pQ:XMJi2*vMiׯѬSufF[Y'_Hm:UtO+R9Tww0f bd?{CƳ :;I1|DXB\"{*lF/_+:UON; p(KIٞ%)CR Uull 1ۋ&ڇ@x\(Ce!g%x Jy|QL].oa\Bfg9>y p;l55%D@aj#ҏ:IQ8s@ZOŃBz ?pT7\w5~I61%;LBR1|Z@qPWڨ6['U8(,avt h/ uv.븠z.ڵ]7\-K=>~EA ,)vi3j sj o뤿$M3| K)wfєS?/2kdj-[ؔ1xd>j bZ)PRΆmFAqL<" "37Cw@fo8%ufz6=BTj RIIi1N`Ɵw֋?^0Xbi\IFYϨr3zHwhH<kS4Jfp'g0 kZ&P1Ux^'R_稿Δ+oC^/SVu&l+H2/hj= AX%9?vT@ N;mW(Y KlC1H>HՖFyisqBORύ\i/A4 1mOm_3=L c[%$n69 s+yDVwk>L>sMq*rp MJ0r݀ɳqUIF|q;H.MzPrj;^=YDu7dFR^6"@Dݏ!a eH(); /p0ض ~ jޢd]!#;֤|7ѯ|B$D[( 3*e;cCGOa.F"b2W]<Kys=Vqz%%Ͼc,=3B8W(3p@7h98afzl߭,cO[Ω`S*\9Îda%B$k6# ]=yB%ߐ+*i5uRC rLF>,F-QbMcio!s sR2>PC8][w<<~Krvpa{ɣS5F+,ך=>5wҕ>fixplqXC#pw.",X7H?q'?'(HgwFBS4lcy@8*CKㆽh Eb< (g v'$N=xєYCLg qQ0a᧜^ݑZY_ƋxCu2>J}q7˖!N/ȏ>;o(zXU>D:ׅ 7Tȇnzoה6eRݮQHM6QBJ͓sR)50޷U"07@ %i6n RjbѧL]fjr 'aG7:ZlH*WGAnQmr/wAʿ,t~Sg")\y7a+&DG/b`2DW.~'~&iq|s#`S)jxۍgJ52gb{jMc}=O1s;L'=P.`L'U1' Q blBf3Cw粞{Sbf~D!.gElGFh`vz%3]7Ҳ9Iz]L<{dc Bl2WjғLɽj(<Jm3ni6$6uV]C;48uKM/n:im9&|}u Qꁛ5ՌI2g/2kN>Aj7Vz>dip|ۅ4}d 7P@%#`ƒdM>SQg`DS7vTU8k=TC_Bx7MpBR̃W! B~j=@E"&)DO:Qzo}ߚH\?,D=ZJ&ҫ# UUw 7G, ʦaֵ?+FT/ q"rݢ h?{pi5krT/΃n Pc ]ۺkG y\%3i6gKĨR$+4|XF(IK}]Rm2uVX0y0[C VM0i!uPf#$f5!$7'#n/Cnxb"] V}o[ RݕEU6MGY NY9LK gqJĮ6%tūMWz+%cIv_R"AE(y540F58Ȝ-K .]E,|:,Ĕ!׶:W+9W2FfJe蕾<# Sq^"mqd5DvΙ%6!h'-9r#Rl$@ti Jk+ FraLU˅ UX VYv4#< X] m[/ݘYT?T'DLVkwK8?7|ӰOsX>I73!Z8 ühӚ,1K!/wgg aP?N;'CieeBҗ,6G ?=?^9` k 4DZV_"c-NXrkyLuԿv7y"gѡs\.dMMweVXz oQ;^H)f,`3KT Ҁ_{z>o ] # [Kq}׀x=.oq0o劀fk6RC_sV/l׽NԎ}d8j 團b.:Rq @Vp YW y9_$bJfQ#+S@8ώΩw)q͏87ءUf3+Nr~ۗ{=mFtψҬkKq%jvDnK6rFԟ.ΰ rqX⨚&-~{:ae&0zJˢ(EVU^pb,~wwЍImyX^c:53{g_.۝sGCBMh (7 g˧ lCeB"jłcHW^ kr)_][$tËoC"!tR?ɸb+$RVU]{U ,H߿3ƕpn,6qy,UBDKp2?c2%*2w$z0[c04ya^I,&t@/[TPP=0'Ī웮bP)\}cd'6 P*45CZ]Ǯa8O9nAk9=Jbf*d(whWeeWpjxk$M.l/"ӵ>5$ k))4uaٖu:k:+-  3m_=DgF} gAڹf1MsWu#]G5SPitR,XF -Tŧ?q[dϘJ;H,Mtimqۓ4N>Wh ^e,+̴i)9?NEwѭ}_cd?.}}m_#DS(2` ALO4 ؾe7`nJLս $Ѝ8L `\Cxݛ?59-&]tk\&E?P5s\{q ErqykUYָD@0@j^r%P9*P=PaXN7.᥯:Лc1IbK,It;!acާ/B15G:\1pެ>sߟ`)Kî5M?F,R\x1sBӆw>孽 hOHXޕtX50y+׼^E3PorԑOa4%X8*84۵L1@;L`4z?;)iI{ef$G! VFq%rUT5OEqavXTE&πoEAȋ! 38 af<-k-~5S)<,z-X _DxLjR%aK˽Ԭ7nAfh PH!{M!#*/Yܩl'[B`bwֺUP'4"hYAþͼ}O2vHh|$Mehc j Ž#XԘAzEW{t3C/ *tRO2D7Rϫy(NlU7)sbq_} ˁ}Tg 7$p@6[ ӎ:ȸwPԆ?:bÿU:wb!@9Y жGHCY2MO∐#w2],Pxx݈daktf;jOyANJ8Hu65غ_"TilSQ"m0^l"=0t9h۶e%<۵lɅEWz&ďqwPFGhQS/>SKƫSw ]66?"]k~#؛T Y8 eu>ј*3Zg93I̾:b$Q;V8_jv҇lξfOz[c_=B&N|"4/OnֹUœH2x ͝! ,AKBa%sH6Yp[[+?~UiquƄByIbS!g[""%xk,.z(?KFz@D{Rkɪz_Zя:< nxd{ivQ&Fk,u\hLy S>X/Sgo_dV&~,=k) 쑭wDreE%V! +/i@9"`t?]&1 ǂD"9'6jT L+n5:|LSF^[.(ҴiU$%[erZ}U G,鴂^tq%:_(//c|o(z*"|)I"Ek%֟c1uLtlQ:0,9Fum+jZm4di6<$ku.ߪTz͊uz01EIE_kNDOм vv<%21GL\kouyഝK ?/ץ#ΈmЇ rzQ+p~ ~|;]o,#> endobj 271 0 obj << /Length1 1630 /Length2 17552 /Length3 0 /Length 18400 /Filter /FlateDecode >> stream xڬctf]&bm۶m;ܱ+m;۩ضm;|o>=c5qM\sͱ7 = oeg`'%Klf+g%#q23Z9؋͸fQ333 dea P)kPhz:[Y:8ڙB_;fs+[3RB^ afodd Pt12Z;Q>LM)͙/3hfb-;`dd_ `@g'+G oTEQ 4`埒 W 4w܁26Z9;y_i8[[g'3 #'S[3g0 _7rtÿgV@g3[szX&1Mc[X23+R&M]CjQ33T02u2;P߱LG-FB%cdg_5L h-BagYT hb 07۳M͜lrN27_3(KI o Pon9yFXE cd{& / <:fdWϓ7q0gtTF Q89%_ oqܛ.9Yekr&Du{ ;68pU~ oxy M}8ҋiKѓbv݇e'~)BF66;~$T3?k?:#Ij},F'r#jm9y[5:K8Dyh֋*~~2ӹ8 ƥ+W 8/wQKX}@tX'g}ed"cP=$l"׈@:L5oRk02%I-]^qqe rFư։Dȅ;)RA 2ihFnՆiZ5q[CGrgsL(p>fZ&:a "FZOd^pz~C֝湬aŕY,2jUE}GDѻYW'Y}M3`]H{gcM0Ʊ ưXx7mP.08CknAYElJn$ mDԍv!gGGSvhs ib8CuYO0_6! {}d(Ä#z=UϘfs"kG:1KU;Y8zPu0`E(Fi]Va,HVtZX9 B1EAG5rV'Oߓ O1iRO>lm'd񨲰ħ`7 \樛2YA~c QZtMgo:oi'N^4Z\:%^}1 oX-mo?4+a[CT]3XEX({(jCr'0 8"e+"Vȵu{=X[:h!6H_o<8S+'Qa1?Dܡ9iCqq݅mm8IE q-lLiLAaި9kѦx ;q~B426"c*`77Jڂ մ'OUJǜ::x5^b#`i)+N K ~ԦzpƝNsHg;SBv˗(nqZϣO-=(^@GFkniJ !Lh@M~R`6`L$zq @i})ޅgDB(Iy *%mŠO*!\FT砸 `qv;i[Hn[2QM))C g'Ҁ yS7V+z8>w )HNMYڰrM{W>cAa"9L|;ʠeU2p?U.V+',@'vYG;o>O%ҩFOMs'6őxoRZB&{h`o/f(8n-8LM/' V?Un؜c qm^euHh5({'bԪ"cʧPepя2v8YV^~M'i5Pn'uڦ5OBn][m‰%޳^i&E9C#l,MƉlVt)t@E @5 iM'=6$B*R!^my8z\h GPq@ث`4h#h _f1y.<AjZ91Ȑ<מ~rV[81ԫ51,R!Ne/$ep xFĞQȺja{v*P%Xf6XX4fG ԉׁf**'&Ty5ndF..eNXS̛щAB_:hc1du û dϰR9.w+T9iר*#쮪Yp}U6bu2(wcjK=DT/4i}|ya4,2x 8Y[˭Iw9I}\Z ٦vX+j"iB>YH<:'$P~x(h(/OI: 5 G[}_㢮nT+_m3c 9i!0,x|!xw,c8m`p3W̸̭ 0Ua Ir:HLnVߛFC*46vaIzT RXW(AU_{_`˚aEꦛ|;j )My! @$`Tm5Zb$CC3۹~j0:aLP~lw]}Ǜڼrdz9zZMnCkoS?PH&X)GzI3I+qje9<6"[ iXĮ*nzѰ4I95%lV"k,vz~Jxҋ-"lT PVn}[n'i%q>Dceii+9$_bC(xw5vTJxUl# EavN>p|J6QƓd؄kC&_HǑy^(CNx2C ;˂(H钅rGh:2E8cKmnEX F;{-_wyە `W#2sLHQ[v:(*3WI']JN*L3LrbHWRe\LT?xݓ`vc7NzB8LXqjD!$`Y a&z# }i͎QhycQ:f`J,{X;W+:o+!^J4ͧ{F2T)c2,N䷩LilQS<&QCX^GV'hE {K{ȴ~uk5O1f("~B)o2ή K!Yy!ݪ( Q]t℺8ݭ_I27tmLӢ7Z`{}C rtڪF(&hBaYU$CxCl^B 8;#07h#Fx~OZ{˦qu#u`eh6ZT. m3ݴfbTu8GI$N?4c^ys\o3s:N]Z0 R"g0}\.ۙw $|j2dl87Wx}vߴc0Y=5nRڰvw'qOZ׽qgHKΥeb*Q)|tH.?{]m$ip:;jw6|1wP%:ukеB\isn(>:u6a>/n]T:cB<~1=.b"Tz:Ěy3L\dP(O._x8ZE-?q^ܼW't2K8i m@pfBv]<~%_ eܴܘυx3/ C&Ȑk= tV&#߾[Q~/؈SY-ӴQs?-rP.` 䴓1>o壌$Gj{+q|\q[K?R "JQ`^j…iae0lN~B,H(9IB2|iXwoKbe)f*!njHwZkk>oVj\L3j135H7\ /AE͛.B!$9S[7;/op= Of.2]E_Й9h@G5M~Nf칎>{?gc䎡KXhmo6nr-K[WeF+Fy27$l-\iF47c{vxz$4UN(SЏfIƆY~*&e&=˵ v^]t.f3 q۞ \XaAtG]S6Hd b2_Ir QK4vZsGhss, wu~yZXBT' ؕPHj"~]7+[Ѐ(BBV8ǂ208ĸ2ԥ\o O/%[w_WR6QE@QΤqZz~:Ѧsۊ-ڙ_5F&pF ɦfA] ]? ?wDL?T[ k$-+b?LC岮fr2l}{-Ku8?E 2F),fuɞ"o'kE(T,+;vt_=9 -g>uù^v8Kx xwXJO) )\Tμy'Uq2K!n&K!%tVÔ\dl\]ٝA8h,D)uRs7h[LNnx1ClZºßPPPa,_Sx(̄uulUAV} bOgȽ Hx֎h&VZ~=3!)|{$9x$&QԩA %g&=\;Z^jU(ShN|1OJ;<\[u4hgst._sԛ~3,Y8c<~4aũ}&v^@p2giYe'1o#*W{vK0p}v 4E(MX/kB)a0y+73e6x,iC |t1QRUs"-CIAUX,vOuW(@S.|dms߮2^7F< ºxaX$:?.)\ZOT779o^=0S36"ӑ8tV>Yg;k@ᦏTX_Zn$+59('{R :@ ]bRT9'gʕ jEgi u/&$ԒU֢͘DaAv]kН[k]:6'bdzz{r]U7eiNϐ99$R2† N(j@?Y͜pjCj]m#|.q-0V(w+cj"?ac"}TOӉ0*蹷/u`ÌWx̀F]]߻=e;<l6r *.ދ$!CUu0*h AE]SnL; wmTq* "@v`Iuc-e T `_&K(˵JH.j.u iSA {\ap>l|mAQox"Uؕcd1J#S(Ash~x6Ƕro\5C^Hg&bk(~INwY!~$VE*gS y7M2%QjY~Ɛ+$QTSZ4[t4x9޴"E"%N>KǧG.ReyasZ TmRT!۫<$zT.z5(bRTDW`?\+޾F_:3_JwB^hˠ7:TxfQS X>y#z2gwH;uݳEKo>NkH1OQf ޻+uNC"MUVGۺ'muan`dc?.}FA e10M!aVӟt g|b ٪jӶ@q1,+qIw!nwIf$pHi}Ša"ߞ:xX}ͅ(X$EcV G~~y(XHzFL\y:y߸)N~qM'v|&ܐAoۘ FxHt+5&U ֻߵnI񖽪J!,hF@Y(X ?#Å :߱K LOG}dr KHCT}GscEiQ @TEo暈FΖnVl$wT`Ok (`]t\X%p@Ukf>doG;h_'PZemc1CܸӽO&A#?-Ekx#K&38d]M`uL k38bdEFtVmoRI MYiS6lDRsܐ9o.>R,JԇT`߀9W7rG28o蠭N9f% !ѡbf%7_:? rp e+#mORmtiSq| R5'e _3{mU &GpuEb|[݌M GPԕrQONԟd8mCeܴN 3}@{8/6xM8w#gMKce y!jaw6P?ׇ j5FOϵ%XPC R}G s|Υ}wG`y _xT책*ry3}#q pYF G\} NMZsBs;@d8E-;{$|mx "o TQZZ}<fCp*_Zi5]BNw>հ~}mϿ彦5{'}@ [Rw*j~,!Fe,>>vµ6 S^ i^p78&sNH h%)ZX9|}i]'6)X+Ll=&/)pTjͧDho?&gLSty~5H)ͦ=Wڎ}{l(m9~-<Η$-!۪v ՗J s31B Um+Ua-*iVqq+akVe ੨n*B}Rֿf^xT *\t9#HNW'RK#: ڸ19ATIh"֛,_z<&_3wuQ^ %0ET^cg-:ҕZV1a'IyFS_š+Y:I(3H`jXJ!VVPq"@Z5\FTG TRJ}uɺ/콛L-H#+} m\=dz7>':j8{b``~x]h2mD\E@V3oO5|D1@*`_.U^Om2GL٫$tg}?쐬i5c*>-8*6r}ViNG6r\@iڬNieGGo3LoD%"~t ~4S{[kF0-mA`R umêJx,N7$ \+ HK$;;:q_]U|VnXU[9;W+O9U+5s7̒=k?WXIXg|FgԃbEԽ6-gXJn!x@6w*iJ#:4žb͏ƹPavhRӬH//!j]MXw_&# -/MEg-mioy)UwSҵU6{`X9.Dv 9|88ÂdfKsiyIM2x>8ΐIիR~6ї5WW6j;D.aD_@6*H[Wr = x箬i.AhZ2O$  y9\ Yc7Q`&>֓Tϥ  'j3uR6c)MQ3L0+pʏTN/cwT\GtCl%$%= 7ψ7M IG6_8)>/Pc4({$@>qS& A dգ 릷=XIf.=HdU3~V,Pu%*sB Eaږ]z+5f- lK,\=ڳGS!+Gُ麕Œt/1Xι_*Bc28Y"SwF*/S K m:-w!\ 3 ,ButDB~2RIAt餀_iX]닩&o<4U`* ~8*?ΠDN" @C݄Soz=G$4J-?cp}Lod1^2J Fւq$g6%<|窼 -f,?݂(,"Ofy;_[-"--fN⡧wFp566tmتCD7qϬ(!Cl0JC0&D>$r+9sXҖH<\?~\ik1]M7 RA=c {zOKL7} Oܢa=L%z4փH }$WLD/ (jJJj>.+T&KPn'$''&N(S!NkDCQ'(0XÆi̖xnW$'$̂q-܂򐫡R‹$M|43́Hv>I+Bl xb{fOǼ\*"KCQDRHTZ9l_5F^aa{{e5V_ffr> ).РݍF:eٜ 'uq|S'\-  +0K/xQPL do{s!' ()ZEjrrF2hIJ$W׾'_*[q{/^Țrkʘs-Y-=#^4폛-9Xnt**/Hp+&*|]}z)Qn|^6]m=IcCtc/DJK"T@;: r!;Xglreߧ1CZ׊˃m7$ebXX('&X±f"+݇n] v|e!!/2:%M*c@kGp"RXii@mzH.GeE h++ :C/Lm#hpuɕ0`d:soPwQgOo~zg~S7n#lOp3kd{!̫erni:wf}p[M jBï{0ޱd b8u5.rǔZK-3C{.yWܼ~L\yAn٠n*6iu/JCezlϴd>|92s"<|ꫧnB5h~93|UM,.֋MdWA-v9QD)-O|2e0ܟ3odfGY-` bB\1D~'N;,k"O')H@`_3GOdN;'YTġy5mte`ɎNjè~3šCc :nH]w%Yheik 菳F)6Ϩq~$  *P*x= %]&AA{M2 zV5:֓LُԠN)N#06WDʶ-uVD&,,emxT9'b02jh7J s$V>g+,|@Ē>xM6ղHX)_2/p_&DFk?zl)grYi3V(٥_sڏ/(+m,܌@(.ǫhbӓ+q(3Et"Q|u[A"0'$r»b)2cd<k9:6ҖI^=c`Z&g'k*P7Ae׃2j̪47nC{5s_48hF*Q:u=b:g s%̔ ,W J9ʂ.ZmQoz3Ă,b]랷TO,ۦEOX߮з]5ڢۣD7ćCG승s0C08Un9|.+uLJ'0Jc]m> -I0ec-XoOGӣ(WnJ`F*25zFz! ޷,羿=B\i)Z5ܫuO`4*/<҅8{-4dyUC<KA_* W j8hzfZ D4vVJ_EOn#dsfFZ -"˻) ؅s,s<&?<3OW\˻ӵ`Yb7ة޾ѤQ3F>3WbhM2^,=8SVf$X eq㠀DcՍya[2[WLhI]O Fƀn)~wGTpۅmT- $h xsh= IPsav|E\ImV'#MUƻ-z `2|g=s~~,Wrڪur(jfv]f[-UgPe{}p;-vfI<2/~qXr1Ylġrϱ ?0+8am+3b u"~`L5/'!ʐ4:DT\1.-fZP`wz]o]7ffb2Q/:? ِ3aڛ\)?ra3r&zDmq<#Z&}a$a eî 6w (PkA p8W}|fX*'1FɿEGU]%Kr{у$#4Ma|rQ$ Hט.p$r0_%HiI#=5't[P6ɐYi2|z 7f<!/<ҩ[|39sVob|E&lkGF滉Lbj'2W*/E1-Jwp5q&SQgIvuϺ,F|chLu5ܟU4wYq ; af;pܗ3BςϢ؝4 _mpay3-,mΚ I{}]-ڎ}#UGh;j?c*002QwoXwݹoTD: Z wX{l^%ܘx!l9| lHG q<΂i.~IKYj} U 0FbڏdSpsS?40L@5B3(AרS(W0IõQ[ dtƘz][ JB<A+pJh(gB&7+|LoO˫ĪM.d㏌;{qyOH endstream endobj 272 0 obj << /Type /FontDescriptor /FontName /OCBIJA+NimbusRomNo9L-Regu /Flags 4 /FontBBox [-168 -281 1000 924] /Ascent 678 /CapHeight 651 /Descent -216 /ItalicAngle 0 /StemV 85 /XHeight 450 /CharSet (/A/B/C/D/E/F/H/I/K/L/M/N/O/P/R/S/T/U/V/W/X/Y/Z/a/b/bracketleft/bracketright/c/colon/comma/d/e/eight/emdash/exclamdown/f/fi/five/fl/four/g/h/hyphen/i/j/k/l/m/n/nine/o/one/p/parenleft/parenright/period/q/questiondown/quotedblleft/quotedblright/quoteleft/quoteright/r/s/semicolon/seven/t/three/two/u/v/w/x/y/z/zero) /FontFile 271 0 R >> endobj 273 0 obj << /Length1 1647 /Length2 10064 /Length3 0 /Length 10907 /Filter /FlateDecode >> stream xڭuUTܒ5\{4h5h\;CoYe>t>vU]REE(`a`e(L]>-] &WF h9K@-9@ hp$N K+^CM?.S K{Why_`$Utd2J= S[ h dX88l>AZsf}w@3k 1@';o`dbydofbW_^=^W2g fUN Ong+ px4w0s_++ 1; @wȟ\@9lk  g?+`8-Mmί4n}[&`_yW 3ւ55%ϼ[88 ?3Zh¦yM ߩ E7qU_]lmL^=x]4&]llM 5zOW pm9K܁* k؛lAW v>Zl77^u6j ZLöYu* =@fRt0* w 1>GNnOڿ8yV48z|y2fHbbo:ze89*6x4CYu0 NHH}.X_.PnX~Lc-ϸ3؅oKי <#yНBǴfXvu:a N]sgcDUͰl k?.5,k,^V=NM!m5]$Lٱ4Bp贑$^1o?|+JY"ON-k>bG6P*akY1 +o_͉(W#n L>e kgqH+sOX<{Q{/{e7yK OA*ϛc> I$ioL{wv bAF)fͷԒ' $0|s 3)^h'D$2ӡChТj?>Ao_A_}3YG"e$0ՀM8E2Z8V>F7g=':{Y/F[֯؏n&B-| %xP?p6??ECQr{XZU}G< 3ł`! Bbͱ~5ʹra3Q x[rQVO#3\Y[aPA>9M"n*'QS&mgs\u#yqΪx:.}&$X]_:#\M6FjNk r?G^orT#*ܓ>]rcOUGY+FU}^t9 e橶M_#`!Ikf]~Ew6nAȽNoP$I}50~/!n; 3D~)և/M j@l og6{e'&*twLfjvTK:;/-L6eO7=(@<^5ZE2>^ºvwb%[}eF\C*o0ômFkoEL%F׆sza83NU%KM|uH |r˵q2Do*cӵX^e-] ųsuXh( *+'*n*`?e X`WEbb:ϑ=QfAjk؅ˤb~1[gJ1N* D ean3˚CO^_jaWxV1l1|—Od F ogY,Ǥz'u6=h&EwawhY?k)=޻(XNa]q/űg0<̒Zc{3T!8>Y(^ TIgr1ϼ U/ýmz-ǻ5ZeM$"->4g ; f_#g 1#P0nG ;Oic@ J@4.\Yw]y-\2X+` ʒlbd?ϳXE_l0~iWPkVσ7㒫g dH&#7VdC2uV5f3i}sɕeˬ0}{"p+'(^R%f+_/-"~Js~O3Loζ/cuoW- T1agm rq׃Ųi+Pqx;2K?N.&t$ŞK>vzqBA:v2<ޓ'fqSsՈv o(j |v+L{m'<'EE[B*+|tԥ ZT)G,Ѭ]LTxSƽ);ق =#"LEwΒ6j!հP9 NLc9|Ԩ}cyr>=%g*=<^i`jBʮE9tFPZB(llu@D9kɳ~iƯIg6)&B)dYo,bz&H, 1v@rX;k]>4|،"^0/b֪y15_+mTH>a[Ŋ>tT/1{{$C kMjU:X@yl`Y LXvz%]tT>k+tsk=Rk*59$ѬnLK+L4ҸDY0T"\ SH[}/dq*P62)^3? o#Q)#MrNMhtY~]h~BME-g! 7yÞ x ui ~d<Չ}0C9?́{tV5@@I5ܣ 2}|%Qo6rD t<Ʋ$l1ҋȰ~"_Pixsm15hJ[}A7g]`3VN%)T`RǥXUI5FrЪ4秙'2oɸJ0s7Ah#2b܆ S2jM^1Ǟ=}{YW*A5ց9Y!K t!V> ,Ó' ;iV/uEyC\.78sqB+w^3)UuY>.(< xX'eyZk2yAGE|ڸjnm%=3]]. X,+4<}P+2t:PƸI`A=Y*ÿut۽@5ivw8vmy&[kE'iƤkQ-޻įl2%}SO]R4UP^XeS4w;fcJbO&5/Jy> QsH;O ]E5 08㮫><}' _;踆ȻL_suhZقv;fo%Y7$\;壧T= E\5BLj`\9$X|X/u_ Ȝ`ʐ  W/PxKMt?u4'yCw̙Wac8#~k#8+* 0A-S[-W7ZwͽؐkK ٥m}9* Ci޽CW<R1Po[PeWL/zzY+YLvV- 56uLVϦ9"?kU3pMI'c!7TIz%]8! `V1 3ljuc>FByvi'g䲍zƁ`_JuAMkO;wѶn v>Ng!`VaW{qX N,}0Ҧrz0q&&20y'.pч [T1zw*dCHNR8xYp aky&D2q>֪ =JjYM/\Ecq'ޑzVץ>bTG #ˣU_J7rg _>)4S7 z':)9!@`"CXS. bJDkoΨPPs],ف$UZ ^?}jm&P[,_ y^CB?" m;ۯx^^ǰ'FFPxH½=҅蒌Si+)]M;]k]F @,/ __ /lZڏ7aLٿ$9+\vNږBӡIl;g`(څ[yߩB]ȭEԮߟzㅦ{>S4f0Vϡa ?zg:xPZ.Fe,8Tp.t6+#!xImR/ӭW])`,,¸y,Y𕀋f ~PUg:o(gXF:5y{/VQ w<e_ߊd_*$y68Xὖ'(vD4ќ̸W03h&҈Bd&kKGoᵎ^Gw}1.b]c'/s<)J'J%{=1Qo۟K{$ 0<[ѱͿ=dښ:EA^s5)\Q}V Y@_\?Ӯ8Y>f{ũ
U;1(vmRޡ*bpޯ?|̓BCѫelؘ*F#m-t Uf'Psbٺɏpl'vV.fY;6'xt8-j8LBՁxr'(b~F {l&c?Mr}M/\pHo&QXMрB'<}_/ڣ>>=Gvf4aas$>g I \+u\ Ӯ|OBR!x UX0zoz~x=[h-WDH "cÕ^ڪ0 yqAnzx/}%ȡ3lQ_^tsYzQK]?arUk#Raw4vIrYB'4Dhw} n4; u,7od=Ö/WC'`h|q<%,J2ь62wYj[1;~n F$)P/g$nz?QEra7J|E1Pa,{ANrGeo4XzqU"^v˃|y)')4*Ha԰/xs7c5zIQ\$_0RSߛ櫫xpMx&dLRq\N*7U3ö<ڑ<LJMŋԘ`.Ua oFd u"!g$-aj̩uœ¿?w Rl8\G9xG786a25q E10<3 Qgfv6k\FZ܆&)J[Hjۋڬ$.bxxB/:Ĕ(ȹp!ph]v4WIY)UG({6ůy7g)jl"ߍH-m҈GޚdH:e~eWPXDIi,?vGsriZsZTJf`ڸ)EB}@cH_h)eX47L{NDg Z-11ԉ7AdaG;}JQrdv}cSA槐ZA5EM?/ h hIK@[( ƃƳuܬ5qJfd,:qkQ2 /wn SgVF1Pg 9iWӲ ׈E9v^ʑR*]Di[H|&<}uܓ^Ht}ʷTB#{u똺a@o|dr_*4%V{8w0Z?=Sf,ZͭfGZV{N kM!XZttxWŽ|HO:}a_?}Q,i("sؙ ]w$x a 4*5y%rˆys42{Ň|=g-Ѻ:bG@bɎKlP(?o[DMtws%&edVsIJQ:yBa$Ep1)UhռX焐TΜslݞ8q Fգx?r#jͥq9Ce0ODZ?QTqT  [u&s޽0Pϒ>Uv\bX [3;".];f=IׇܾhmRbt"ka*sBgB2Yh]D>X{]8h\b9/ E pW-vx?^H<|H!p rUKck";/sMWO\m&\V\HLZ؂1P&鹙arh+zߥTMJ$[ٱln;oJFUrVB[o9ׯL4" %_Q~EQnۦ;2k< H!Z4 D֌hNzײ <$΍+̫<6:kB)6Ho䃚v&"UqքO7dO8Jֻtt(dGnQϺO]3(Sl]Tk=RR鉬=A 9Lk(+I!X]q80Ж^HQ&OVDf"_ -3P0Mđ 5jLnJSE1D Ǽm%A,M@xP=8Dxn~*A>Ob4+T:m^451=C t ( w:翄x* p($fTAu&O K~ Col_\X1 05NeΣ|r@?=s0,.!p[wLQ$j~qJ<|ɷ ỡB轏edNnҎ<k'$t7#] #NZzoq.iEF%!gNG\֛_ NHPHpx~btqa|W/vXi~sMIzBؠ֝ jZ6PPM|FM 7Sfs2RPmy 86ʶ,kw@ط Hly1f#ޔkDnRKE=?p-Y^^-CQ~~ԶO]"㳃HxX:<냲CL~ u GXp݌-wi,3v .+*))KБ%kF=aMW tl5POD>JhPv؏ŋ^QbQ  ]6B@>{. ˷'AmEޓq.f [9u) B܉*i-vOXMAU=J1Bdm%(zmIVUlP->J̪jb@Vʭ.\{8/5SF]+/2O9Anyaॽom>2bJS6XL}s6CiuJuyMpXZ<_O|؅H5ӫĥԤ*Apc`"z :6`8 @b6p / <ԲV0^zV$q^,Sb"Qq.;VBD@j|<)B.ʁ6&A׍jSQ̪S=;ڲĹDӮKfXl"a|i3cQ"4p5$KeGsAН`8l,7GGۤЋ/e5-D Ns@&J4Jmi20Xݼ'ڬvKO-"cعO*tu ;SO3WhY߶G#BǠ:T=8~Kxy_ߤGtFCNFMxz^~ 9N*KxW]#c߃_dMc$'m, C..O;mQtrj2'+E9$X$!m[xuI&\JEc6t'BZi/Dhm7͢ԟDY칏[w iG $NZ2<ZbO`b Lgި/-s|pY6{9SUw)`5ysNVpBsE\^VF27)#;@bCVCCoVx`%Vj*lK8T\Xn4uCOKuUzo[~7d"Q F%6]RE a^ѻc%)2 b0Ϧsu+&Fv~IRVp}4ݖ86융dc~}1)ʞ\+<"mN^9HCZP0li}mz"~Ye_಺M =7$ai'O3To%#ܩ=i^ wE05{$7O@܇  ;SW1<6Ժ-\)3Τzl"{w cʕ=`'c/ȝ)p5zŏMXɐocxزRY)Iq=:43qo4JWi8{=`{LN L!w.y>DTf!.gBc mv~-ƷYNa-Z/5fa/v*|6L%~L7ϼRcgk ~!HoJ;6[ ޝfw2;/Ԁ[6"{aWp-àti] 6tt?8D*V[֒3KK+EIb3t명2-zo%xsˤpo(*zH~&Z61ܲ zuIVל[3,j8_7e7 .\˝1.R$3&{+ &eO&ٽᎷ&V+?e endstream endobj 274 0 obj << /Type /FontDescriptor /FontName /ALRKBW+NimbusRomNo9L-ReguItal /Flags 4 /FontBBox [-169 -270 1010 924] /Ascent 669 /CapHeight 669 /Descent -193 /ItalicAngle -15 /StemV 78 /XHeight 441 /CharSet (/A/L/P/R/S/a/b/c/comma/d/e/f/fi/g/h/hyphen/i/k/l/m/n/o/p/period/r/s/t/u/v/x/y) /FontFile 273 0 R >> endobj 247 0 obj << /Type /Encoding /Differences [2/fi/fl 33/exclam/quotedbl/numbersign/dollar/percent 39/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I 75/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft 93/bracketright 95/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright 147/quotedblleft/quotedblright 151/emdash 161/exclamdown 191/questiondown] >> endobj 70 0 obj << /Type /Font /Subtype /Type1 /BaseFont /SYFPBV+CMMI10 /FontDescriptor 256 0 R /FirstChar 60 /LastChar 62 /Widths 246 0 R >> endobj 158 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HAJUBM+CMSY10 /FontDescriptor 258 0 R /FirstChar 110 /LastChar 110 /Widths 244 0 R >> endobj 99 0 obj << /Type /Font /Subtype /Type1 /BaseFont /IUEMPT+CMSY5 /FontDescriptor 260 0 R /FirstChar 99 /LastChar 100 /Widths 245 0 R >> endobj 33 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HRAHZV+NimbusMonL-Bold /FontDescriptor 262 0 R /FirstChar 46 /LastChar 122 /Widths 250 0 R /Encoding 247 0 R >> endobj 34 0 obj << /Type /Font /Subtype /Type1 /BaseFont /VQGLEG+NimbusMonL-Regu /FontDescriptor 264 0 R /FirstChar 33 /LastChar 125 /Widths 249 0 R /Encoding 247 0 R >> endobj 29 0 obj << /Type /Font /Subtype /Type1 /BaseFont /QRWWCH+NimbusSanL-Regu /FontDescriptor 266 0 R /FirstChar 2 /LastChar 151 /Widths 254 0 R /Encoding 247 0 R >> endobj 30 0 obj << /Type /Font /Subtype /Type1 /BaseFont /RCFEMR+NimbusSanL-ReguItal /FontDescriptor 268 0 R /FirstChar 46 /LastChar 115 /Widths 253 0 R /Encoding 247 0 R >> endobj 32 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GARPUW+NimbusRomNo9L-Medi /FontDescriptor 270 0 R /FirstChar 2 /LastChar 151 /Widths 251 0 R /Encoding 247 0 R >> endobj 31 0 obj << /Type /Font /Subtype /Type1 /BaseFont /OCBIJA+NimbusRomNo9L-Regu /FontDescriptor 272 0 R /FirstChar 2 /LastChar 191 /Widths 252 0 R /Encoding 247 0 R >> endobj 56 0 obj << /Type /Font /Subtype /Type1 /BaseFont /ALRKBW+NimbusRomNo9L-ReguItal /FontDescriptor 274 0 R /FirstChar 2 /LastChar 121 /Widths 248 0 R /Encoding 247 0 R >> endobj 35 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [27 0 R 53 0 R 59 0 R 62 0 R 66 0 R 73 0 R] >> endobj 80 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [77 0 R 82 0 R 87 0 R 92 0 R 96 0 R 101 0 R] >> endobj 109 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [105 0 R 112 0 R 116 0 R 119 0 R 126 0 R 129 0 R] >> endobj 135 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [133 0 R 137 0 R 142 0 R 146 0 R 151 0 R 156 0 R] >> endobj 163 0 obj << /Type /Pages /Count 3 /Parent 275 0 R /Kids [160 0 R 165 0 R 241 0 R] >> endobj 275 0 obj << /Type /Pages /Count 27 /Kids [35 0 R 80 0 R 109 0 R 135 0 R 163 0 R] >> endobj 276 0 obj << /Type /Outlines /First 169 0 R /Last 238 0 R /Count 12 >> endobj 238 0 obj << /Title 239 0 R /A 237 0 R /Parent 276 0 R /Prev 232 0 R >> endobj 235 0 obj << /Title 236 0 R /A 234 0 R /Parent 232 0 R >> endobj 232 0 obj << /Title 233 0 R /A 231 0 R /Parent 276 0 R /Prev 229 0 R /Next 238 0 R /First 235 0 R /Last 235 0 R /Count -1 >> endobj 229 0 obj << /Title 230 0 R /A 228 0 R /Parent 276 0 R /Prev 223 0 R /Next 232 0 R >> endobj 226 0 obj << /Title 227 0 R /A 225 0 R /Parent 223 0 R >> endobj 223 0 obj << /Title 224 0 R /A 222 0 R /Parent 276 0 R /Prev 220 0 R /Next 229 0 R /First 226 0 R /Last 226 0 R /Count -1 >> endobj 220 0 obj << /Title 221 0 R /A 219 0 R /Parent 276 0 R /Prev 214 0 R /Next 223 0 R >> endobj 217 0 obj << /Title 218 0 R /A 216 0 R /Parent 214 0 R >> endobj 214 0 obj << /Title 215 0 R /A 213 0 R /Parent 276 0 R /Prev 211 0 R /Next 220 0 R /First 217 0 R /Last 217 0 R /Count -1 >> endobj 211 0 obj << /Title 212 0 R /A 210 0 R /Parent 276 0 R /Prev 196 0 R /Next 214 0 R >> endobj 208 0 obj << /Title 209 0 R /A 207 0 R /Parent 205 0 R >> endobj 205 0 obj << /Title 206 0 R /A 204 0 R /Parent 196 0 R /Prev 199 0 R /First 208 0 R /Last 208 0 R /Count -1 >> endobj 202 0 obj << /Title 203 0 R /A 201 0 R /Parent 199 0 R >> endobj 199 0 obj << /Title 200 0 R /A 198 0 R /Parent 196 0 R /Next 205 0 R /First 202 0 R /Last 202 0 R /Count -1 >> endobj 196 0 obj << /Title 197 0 R /A 195 0 R /Parent 276 0 R /Prev 193 0 R /Next 211 0 R /First 199 0 R /Last 205 0 R /Count -2 >> endobj 193 0 obj << /Title 194 0 R /A 192 0 R /Parent 276 0 R /Prev 181 0 R /Next 196 0 R >> endobj 190 0 obj << /Title 191 0 R /A 189 0 R /Parent 181 0 R /Prev 187 0 R >> endobj 187 0 obj << /Title 188 0 R /A 186 0 R /Parent 181 0 R /Prev 184 0 R /Next 190 0 R >> endobj 184 0 obj << /Title 185 0 R /A 183 0 R /Parent 181 0 R /Next 187 0 R >> endobj 181 0 obj << /Title 182 0 R /A 180 0 R /Parent 276 0 R /Prev 172 0 R /Next 193 0 R /First 184 0 R /Last 190 0 R /Count -3 >> endobj 178 0 obj << /Title 179 0 R /A 177 0 R /Parent 172 0 R /Prev 175 0 R >> endobj 175 0 obj << /Title 176 0 R /A 174 0 R /Parent 172 0 R /Next 178 0 R >> endobj 172 0 obj << /Title 173 0 R /A 171 0 R /Parent 276 0 R /Prev 169 0 R /Next 181 0 R /First 175 0 R /Last 178 0 R /Count -2 >> endobj 169 0 obj << /Title 170 0 R /A 168 0 R /Parent 276 0 R /Next 172 0 R >> endobj 277 0 obj << /Names [(label-basic-mapping) 108 0 R (label-elements) 69 0 R (label-intro) 55 0 R (label-module-ZConfig) 90 0 R (label-module-ZConfig.cmdline) 153 0 R (label-module-ZConfig.datatypes) 144 0 R] /Limits [(label-basic-mapping) (label-module-ZConfig.datatypes)] >> endobj 278 0 obj << /Names [(label-module-ZConfig.loader) 148 0 R (label-module-ZConfig.substitution) 71 0 R (label-schema-components) 94 0 R (label-schema-dtd) 162 0 R (label-standard-components) 107 0 R (label-standard-datatypes) 98 0 R] /Limits [(label-module-ZConfig.loader) (label-standard-datatypes)] >> endobj 279 0 obj << /Names [(label-syntax) 57 0 R (label-writing-schema) 68 0 R (page002) 36 0 R (page003) 37 0 R (page004) 38 0 R (page005) 39 0 R] /Limits [(label-syntax) (page005)] >> endobj 280 0 obj << /Names [(page006) 75 0 R (page007) 79 0 R (page008) 84 0 R (page009) 89 0 R (page010) 40 0 R (page011) 41 0 R] /Limits [(page006) (page011)] >> endobj 281 0 obj << /Names [(page012) 103 0 R (page013) 42 0 R (page014) 114 0 R (page015) 43 0 R (page016) 121 0 R (page017) 44 0 R] /Limits [(page012) (page017)] >> endobj 282 0 obj << /Names [(page018) 131 0 R (page019) 45 0 R (page020) 139 0 R (page021) 46 0 R (page022) 47 0 R (page023) 48 0 R] /Limits [(page018) (page023)] >> endobj 283 0 obj << /Names [(page024) 49 0 R (page025) 50 0 R (page026) 167 0 R (page027) 243 0 R] /Limits [(page024) (page027)] >> endobj 284 0 obj << /Kids [277 0 R 278 0 R 279 0 R 280 0 R 281 0 R 282 0 R] /Limits [(label-basic-mapping) (page023)] >> endobj 285 0 obj << /Kids [283 0 R] /Limits [(page024) (page027)] >> endobj 286 0 obj << /Kids [284 0 R 285 0 R] /Limits [(label-basic-mapping) (page027)] >> endobj 287 0 obj << /Dests 286 0 R >> endobj 288 0 obj << /Type /Catalog /Pages 275 0 R /Outlines 276 0 R /Names 287 0 R /PageMode /UseOutlines >> endobj 289 0 obj << /Producer (pdfTeX-1.40.3) /Author (Zope Corporation) /Title (ZConfig Package Reference) /Creator (TeX) /CreationDate (D:20100413091707-04'00') /ModDate (D:20100413091707-04'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX using libpoppler, Version 3.141592-1.40.3-2.2 (Web2C 7.5.6) kpathsea version 3.5.6) >> endobj xref 0 290 0000000000 65535 f 0000002420 00000 n 0000002571 00000 n 0000002702 00000 n 0000002832 00000 n 0000002967 00000 n 0000003102 00000 n 0000003233 00000 n 0000003367 00000 n 0000003502 00000 n 0000003637 00000 n 0000003768 00000 n 0000003900 00000 n 0000004033 00000 n 0000004170 00000 n 0000004305 00000 n 0000004442 00000 n 0000004574 00000 n 0000004706 00000 n 0000004842 00000 n 0000004974 00000 n 0000005106 00000 n 0000005241 00000 n 0000005373 00000 n 0000005503 00000 n 0000008282 00000 n 0000005636 00000 n 0000002141 00000 n 0000000015 00000 n 0000181001 00000 n 0000181170 00000 n 0000181516 00000 n 0000181344 00000 n 0000180661 00000 n 0000180831 00000 n 0000181864 00000 n 0000008598 00000 n 0000010770 00000 n 0000013016 00000 n 0000016433 00000 n 0000032885 00000 n 0000036184 00000 n 0000041742 00000 n 0000046227 00000 n 0000051321 00000 n 0000056137 00000 n 0000062115 00000 n 0000065874 00000 n 0000069424 00000 n 0000072769 00000 n 0000074534 00000 n 0000008413 00000 n 0000008772 00000 n 0000008148 00000 n 0000005766 00000 n 0000008653 00000 n 0000181688 00000 n 0000008713 00000 n 0000010825 00000 n 0000010662 00000 n 0000008890 00000 n 0000013071 00000 n 0000012908 00000 n 0000010931 00000 n 0000016274 00000 n 0000016608 00000 n 0000016147 00000 n 0000013177 00000 n 0000016488 00000 n 0000016548 00000 n 0000180232 00000 n 0000072825 00000 n 0000019688 00000 n 0000019525 00000 n 0000016750 00000 n 0000019633 00000 n 0000023132 00000 n 0000022969 00000 n 0000019818 00000 n 0000023077 00000 n 0000181974 00000 n 0000026095 00000 n 0000025932 00000 n 0000023274 00000 n 0000026040 00000 n 0000029530 00000 n 0000029733 00000 n 0000029403 00000 n 0000026225 00000 n 0000029678 00000 n 0000056193 00000 n 0000033000 00000 n 0000032777 00000 n 0000029875 00000 n 0000032940 00000 n 0000036299 00000 n 0000036076 00000 n 0000033142 00000 n 0000036239 00000 n 0000180519 00000 n 0000039124 00000 n 0000038956 00000 n 0000036453 00000 n 0000039067 00000 n 0000041922 00000 n 0000041630 00000 n 0000039243 00000 n 0000041798 00000 n 0000041860 00000 n 0000182085 00000 n 0000046046 00000 n 0000043413 00000 n 0000043244 00000 n 0000042029 00000 n 0000043356 00000 n 0000046283 00000 n 0000045914 00000 n 0000043520 00000 n 0000048138 00000 n 0000047969 00000 n 0000046402 00000 n 0000048081 00000 n 0000050602 00000 n 0000050778 00000 n 0000050947 00000 n 0000051377 00000 n 0000050454 00000 n 0000048245 00000 n 0000053407 00000 n 0000053238 00000 n 0000051496 00000 n 0000053350 00000 n 0000056249 00000 n 0000056025 00000 n 0000053514 00000 n 0000182202 00000 n 0000060052 00000 n 0000059883 00000 n 0000056380 00000 n 0000059995 00000 n 0000061964 00000 n 0000062228 00000 n 0000061832 00000 n 0000060183 00000 n 0000062171 00000 n 0000065987 00000 n 0000065762 00000 n 0000062347 00000 n 0000065930 00000 n 0000069268 00000 n 0000069537 00000 n 0000069136 00000 n 0000066118 00000 n 0000069480 00000 n 0000072618 00000 n 0000072881 00000 n 0000072486 00000 n 0000069668 00000 n 0000180374 00000 n 0000074652 00000 n 0000074422 00000 n 0000073025 00000 n 0000074590 00000 n 0000182319 00000 n 0000075754 00000 n 0000075585 00000 n 0000074747 00000 n 0000075697 00000 n 0000075837 00000 n 0000184795 00000 n 0000075882 00000 n 0000075917 00000 n 0000184663 00000 n 0000075962 00000 n 0000076005 00000 n 0000184584 00000 n 0000076050 00000 n 0000076108 00000 n 0000184505 00000 n 0000076153 00000 n 0000076207 00000 n 0000184373 00000 n 0000076252 00000 n 0000076303 00000 n 0000184294 00000 n 0000076348 00000 n 0000076388 00000 n 0000184201 00000 n 0000076433 00000 n 0000076475 00000 n 0000184122 00000 n 0000076520 00000 n 0000076574 00000 n 0000184029 00000 n 0000076619 00000 n 0000076667 00000 n 0000183897 00000 n 0000076712 00000 n 0000076769 00000 n 0000183779 00000 n 0000076814 00000 n 0000076862 00000 n 0000183714 00000 n 0000076907 00000 n 0000076952 00000 n 0000183596 00000 n 0000076997 00000 n 0000077046 00000 n 0000183531 00000 n 0000077091 00000 n 0000077139 00000 n 0000183438 00000 n 0000077184 00000 n 0000077239 00000 n 0000183306 00000 n 0000077284 00000 n 0000077345 00000 n 0000183241 00000 n 0000077390 00000 n 0000077425 00000 n 0000183148 00000 n 0000077470 00000 n 0000077540 00000 n 0000183016 00000 n 0000077585 00000 n 0000077650 00000 n 0000182951 00000 n 0000077695 00000 n 0000077733 00000 n 0000182858 00000 n 0000077778 00000 n 0000077850 00000 n 0000182726 00000 n 0000077895 00000 n 0000077962 00000 n 0000182661 00000 n 0000078007 00000 n 0000078040 00000 n 0000182582 00000 n 0000078085 00000 n 0000078594 00000 n 0000078425 00000 n 0000078139 00000 n 0000078537 00000 n 0000078677 00000 n 0000078700 00000 n 0000078731 00000 n 0000179697 00000 n 0000078766 00000 n 0000079237 00000 n 0000079628 00000 n 0000079955 00000 n 0000080534 00000 n 0000081264 00000 n 0000081564 00000 n 0000082142 00000 n 0000083950 00000 n 0000084183 00000 n 0000085420 00000 n 0000085650 00000 n 0000086997 00000 n 0000087239 00000 n 0000099950 00000 n 0000100258 00000 n 0000119676 00000 n 0000120262 00000 n 0000129395 00000 n 0000129795 00000 n 0000133931 00000 n 0000134197 00000 n 0000148880 00000 n 0000149292 00000 n 0000167813 00000 n 0000168355 00000 n 0000179383 00000 n 0000182412 00000 n 0000182504 00000 n 0000184874 00000 n 0000185156 00000 n 0000185466 00000 n 0000185653 00000 n 0000185817 00000 n 0000185984 00000 n 0000186150 00000 n 0000186282 00000 n 0000186403 00000 n 0000186472 00000 n 0000186561 00000 n 0000186599 00000 n 0000186708 00000 n trailer << /Size 290 /Root 288 0 R /Info 289 0 R /ID [<90874B4389AB3A5518592246B83738E2> <90874B4389AB3A5518592246B83738E2>] >> startxref 187044 %%EOF ZConfig-3.1.0/doc/zconfig.tex0000644000076500000240000022327212610530667016134 0ustar do3ccstaff00000000000000%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Copyright (c) 2002-2007 Zope Foundation and Contributors. % All Rights Reserved. % % This software is subject to the provisions of the Zope Public License, % Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. % THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED % WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED % WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS % FOR A PARTICULAR PURPOSE. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \documentclass{howto} \usepackage{xmlmarkup} \newcommand{\datatype}[1]{\strong{#1}} \title{ZConfig Package Reference} \date{13 April 2010} \release{2.8.0} \setshortversion{2.8} \author{Zope Corporation} \authoraddress{\url{http://www.zope.com/}} \begin{document} \maketitle \begin{abstract} \noindent This document describes the syntax and API used in configuration files for components of a Zope installation written by Zope Corporation. This configuration mechanism is itself configured using a schema specification written in XML. \end{abstract} \tableofcontents \section{Introduction \label{intro}} Zope uses a common syntax and API for configuration files designed for software components written by Zope Corporation. Third-party software which is also part of a Zope installation may use a different syntax, though any software is welcome to use the syntax used by Zope Corporation. Any software written in Python is free to use the \module{ZConfig} software to load such configuration files in order to ensure compatibility. This software is covered by the Zope Public License, version 2.1. The \module{ZConfig} package has been tested with Python 2.3. Older versions of Python are not supported. \module{ZConfig} only relies on the Python standard library. Configurations which use \module{ZConfig} are described using \dfn{schema}. A schema is a specification for the allowed structure and content of the configuration. \module{ZConfig} schema are written using a small XML-based language. The schema language allows the schema author to specify the names of the keys allowed at the top level and within sections, to define the types of sections which may be used (and where), the types of each values, whether a key or section must be specified or is optional, default values for keys, and whether a value can be given only once or repeatedly. \section{Configuration Syntax \label{syntax}} Like the \ulink{\module{ConfigParser}} {http://docs.python.org/library/configparser.html} format, this format supports key-value pairs arranged in sections. Unlike the \module{ConfigParser} format, sections are typed and can be organized hierarchically. Additional files may be included if needed. Schema components not specified in the application schema can be imported from the configuration file. Though both formats are substantially line-oriented, this format is more flexible. The intent of supporting nested section is to allow setting up the configurations for loosely-associated components in a container. For example, each process running on a host might get its configuration section from that host's section of a shared configuration file. The top level of a configuration file consists of a series of inclusions, key-value pairs, and sections. Comments can be added on lines by themselves. A comment has a \character{\#} as the first non-space character and extends to the end of the line: \begin{verbatim} # This is a comment \end{verbatim} An inclusion is expressed like this: \begin{verbatim} %include defaults.conf \end{verbatim} The resource to be included can be specified by a relative or absolute URL, resolved relative to the URL of the resource the \keyword{\%include} directive is located in. A key-value pair is expressed like this: \begin{verbatim} key value \end{verbatim} The key may include any non-white characters except for parentheses. The value contains all the characters between the key and the end of the line, with surrounding whitespace removed. Since comments must be on lines by themselves, the \character{\#} character can be part of a value: \begin{verbatim} key value # still part of the value \end{verbatim} Sections may be either empty or non-empty. An empty section may be used to provide an alias for another section. A non-empty section starts with a header, contains configuration data on subsequent lines, and ends with a terminator. The header for a non-empty section has this form (square brackets denote optional parts): \begin{alltt} <\var{section-type} \optional{\var{name}} > \end{alltt} \var{section-type} and \var{name} all have the same syntactic constraints as key names. The terminator looks like this: \begin{alltt} \end{alltt} The configuration data in a non-empty section consists of a sequence of one or more key-value pairs and sections. For example: \begin{verbatim} key-1 value-1 key-2 value-2 key-3 value-3 \end{verbatim} (The indentation is used here for clarity, but is not required for syntactic correctness.) The header for empty sections is similar to that of non-empty sections, but there is no terminator: \begin{alltt} <\var{section-type} \optional{\var{name}} /> \end{alltt} \subsection{Extending the Configuration Schema} As we'll see in section~\ref{writing-schema}, ``Writing Configuration Schema,'' what can be written in a configuration is controlled by schemas which can be built from \emph{components}. These components can also be used to extend the set of implementations of objects the application can handle. What this means when writing a configuration is that third-party implementations of application object types can be used wherever those application types are used in the configuration, if there's a \module{ZConfig} component available for that implementation. The configuration file can use an \keyword{\%import} directive to load a named component: \begin{verbatim} %import Products.Ape \end{verbatim} The text to the right of the \keyword{\%import} keyword must be the name of a Python package; the \module{ZConfig} component provided by that package will be loaded and incorporated into the schema being used to load the configuration file. After the import, section types defined in the component may be used in the configuration. More detail is needed for this to really make sense. A schema may define section types which are \emph{abstract}; these cannot be used directly in a configuration, but multiple concrete section types can be defined which \emph{implement} the abstract types. Wherever the application allows an abstract type to be used, any concrete type which implements that abstract type can be used in an actual configuration. The \keyword{\%import} directive allows loading schema components which provide alternate concrete section types which implement the abstract types defined by the application. This allows third-party implementations of abstract types to be used in place of or in addition to implementations provided with the application. Consider an example application application which supports logging in the same way Zope 2 does. There are some parameters which configure the general behavior of the logging mechanism, and an arbitrary number of \emph{log handlers} may be specified to control how the log messages are handled. Several log handlers are provided by the application. Here is an example logging configuration: \begin{verbatim} level verbose path /var/log/myapp/events.log \end{verbatim} A third-party component may provide a log handler to send high-priority alerts the system administrator's text pager or SMS-capable phone. All that's needed is to install the implementation so it can be imported by Python, and modify the configuration: \begin{verbatim} %import my.pager.loghandler level verbose path /var/log/myapp/events.log number 1-800-555-1234 message Something broke! \end{verbatim} \subsection{Textual Substitution in Values} \module{ZConfig} provides a limited way to re-use portions of a value using simple string substitution. To use this facility, define named bits of replacement text using the \keyword{\%define} directive, and reference these texts from values. The syntax for \keyword{\%define} is: \begin{alltt} %define \var{name} \optional{\var{value}} \end{alltt} The value of \var{name} must be a sequence of letters, digits, and underscores, and may not start with a digit; the namespace for these names is separate from the other namespaces used with \module{ZConfig}, and is case-insensitive. If \var{value} is omitted, it will be the empty string. If given, there must be whitespace between \var{name} and \var{value}; \var{value} will not include any whitespace on either side, just like values from key-value pairs. Names must be defined before they are used, and may not be re-defined with a different value. All resources being parsed as part of a configuration share a single namespace for defined names. References to defined names from configuration values use the syntax described for the \refmodule{ZConfig.substitution} module. Configuration values which include a \character{\$} as part of the actual value will need to use \code{\$\$} to get a single \character{\$} in the result. The values of defined names are processed in the same way as configuration values, and may contain references to named definitions. For example, the value for \code{key} will evaluate to \code{value}: \begin{verbatim} %define name value key $name \end{verbatim} %$ <-- bow to font-lock \section{Writing Configuration Schema \label{writing-schema}} \module{ZConfig} schema are written as XML documents. Data types are searched in a special namespace defined by the data type registry. The default registry has slightly magical semantics: If the value can be matched to a standard data type when interpreted as a \datatype{basic-key}, the standard data type will be used. If that fails, the value must be a \datatype{dotted-name} containing at least one dot, and a conversion function will be sought using the \method{search()} method of the data type registry used to load the schema. \subsection{Schema Elements \label{elements}} For each element, the content model is shown, followed by a description of how the element is used, and then a list of the available attributes. For each attribute, the type of the value is given as either the name of a \module{ZConfig} datatype or an XML attribute value type. Familiarity with XML's Document Type Definition language is helpful. The following elements are used to describe a schema: \begin{elementdesc}{schema}{description?, metadefault?, example?, import*, (sectiontype | abstracttype)*, (section | key | multisection | multikey)*} Document element for a \module{ZConfig} schema. \begin{attributedesc}{extends}{\datatype{space-separated-url-references}} A list of URLs of base schemas from which this section type will inherit key, section, and section type declarations. If omitted, this schema is defined using only the keys, sections, and section types contained within the \element{schema} element. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this section. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If any base schemas are listed in the \attribute{extends} attribute, the default value for this attribute comes from the base schemas. If the base schemas all use the same \attribute{datatype}, then that data type will be the default value for the extending schema. If there are no base schemas, the default value is \datatype{null}, which means that the \module{ZConfig} section object will be used unconverted. If the base schemas have different \attribute{datatype} definitions, you must explicitly define the \attribute{datatype} in the extending schema. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{keytype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to keys found in this section. This can be used to constrain key values in different ways; two data types which may be especially useful are the \datatype{identifier} and \datatype{ipaddr-or-hostname} types. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If any base schemas are listed in the \attribute{extends} attribute, the default value for this attribute comes from the base schemas. If the base schemas all use the same \attribute{keytype}, then that key type will be the default value for the extending schema. If there are no base schemas, the default value is \datatype{basic-key}. If the base schemas have different \attribute{keytype} definitions, you must explicitly define the \attribute{keytype} in the extending schema. \end{attributedesc} \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts with the \element{schema} element if it hasn't been overridden by an inner element with a \attribute{prefix} attribute. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{description}{PCDATA} Descriptive text explaining the purpose the container of the \element{description} element. Most other elements can contain a \element{description} element as their first child. At most one \element{description} element may appear in a given context. \begin{attributedesc}{format}{NMTOKEN} Optional attribute that can be added to indicate what conventions are used to mark up the contained text. This is intended to serve as a hint for documentation extraction tools. Suggested values are: \begin{tableii}{l|l}{code}{Value}{Content Format} \lineii{plain}{\mimetype{text/plain}; blank lines separate paragraphs} \lineii{rest}{reStructuredText} \lineii{stx}{Classic Structured Text} \end{tableii} \end{attributedesc} \end{elementdesc} \begin{elementdesc}{example}{PCDATA} An example value. This serves only as documentation. \end{elementdesc} \begin{elementdesc}{metadefault}{PCDATA} A description of the default value, for human readers. This may include information about how a computed value is determined when the schema does not specify a default value. \end{elementdesc} \begin{elementdesc}{abstracttype}{description?} Define an abstract section type. \begin{attributedesc}{name}{\datatype{basic-key}} The name of the abstract section type; required. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{sectiontype}{description?, (section | key | multisection | multikey)*} Define a concrete section type. \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this section. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If \attribute{datatype} is omitted and \attribute{extends} is used, the \attribute{datatype} from the section type identified by the \attribute{extends} attribute is used. \end{attributedesc} \begin{attributedesc}{extends}{\datatype{basic-key}} The name of a concrete section type from which this section type acquires all key and section declarations. This type does \emph{not} automatically implement any abstract section type implemented by the named section type. If omitted, this section is defined with only the keys and sections contained within the \element{sectiontype} element. The new section type is called a \emph{derived} section type, and the type named by this attribute is called the \emph{base} type. Values for the \attribute{datatype} and \attribute{keytype} attributes are acquired from the base type if not specified. \end{attributedesc} \begin{attributedesc}{implements}{\datatype{basic-key}} The name of an abstract section type which this concrete section type implements. If omitted, this section type does not implement any abstract type, and can only be used if it is specified directly in a schema or other section type. \end{attributedesc} \begin{attributedesc}{keytype}{\datatype{basic-key}} The data type converter which will be applied to keys found in this section. This can be used to constrain key values in different ways; two data types which may be especially useful are the \datatype{identifier} and \datatype{ipaddr-or-hostname} types. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. The default value is \datatype{basic-key}. If \attribute{keytype} is omitted and \attribute{extends} is used, the \attribute{keytype} from the section type identified by the \attribute{extends} attribute is used. \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the section type; required. \end{attributedesc} \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts in the \element{sectiontype} element. If omitted, the prefix specified by a containing context is used if specified. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{import}{EMPTY} Import a schema component. Exactly one of the attributes \attribute{package} and \attribute{src} must be specified. \begin{attributedesc}{file}{file name without directory information} Name of the component file within a package; if not specified, \file{component.xml} is used. This may only be given when \attribute{package} is used. (The \file{component.xml} file is always used when importing via \keyword{\%import} from a configuration file.) \end{attributedesc} \begin{attributedesc}{package}{\datatype{dotted-suffix}} Name of a Python package that contains the schema component being imported. The component will be loaded from the file identified by the \attribute{file} attribute, or \file{component.xml} if \attribute{file} is not specified. If the package name given starts with a dot (\character{.}), the name used will be the current prefix and the value of this attribute concatenated. \end{attributedesc} \begin{attributedesc}{src}{\datatype{url-reference}} URL to a separate schema which can provide useful types. The referenced resource must contain a schema, not a schema component. Section types defined or imported by the referenced schema are added to the schema containing the \element{import}; top-level keys and sections are ignored. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{key}{description?, example?, metadefault?, default*} A \element{key} element is used to describe a key-value pair which may occur at most once in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this key should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the key name to underscores. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this key. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. \end{attributedesc} \begin{attributedesc}{default}{\datatype{string}} If the key-value pair is optional and this attribute is specified, the value of this attribute will be converted using the appropriate data type converter and returned to the application as the configured value. This attribute may not be specified if the \attribute{required} attribute is \code{yes}. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the key, as it must be given in a configuration instance, or `\code{*}'. If the value is `\code{*}', any name not already specified as a key may be used, and the configuration value for the key will be a dictionary mapping from the key name to the value. In this case, the \attribute{attribute} attribute must be specified, and the data type for the key will be applied to each key which is found. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the key. If the value is \code{yes}, the \attribute{default} attribute may not be specified and an error will be reported if the configuration instance does not specify a value for the key. If the value is \code{no} (the default) and the configuration instance does not specify a value, the value reported to the application will be that specified by the \attribute{default} attribute, if given, or \code{None}. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{multikey}{description?, example?, metadefault?, default*} A \element{multikey} element is used to describe a key-value pair which may occur any number of times in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this key should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the key name to underscores. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this key. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the key, as it must be given in a configuration instance, or `\code{+}'. If the value is `\code{+}', any name not already specified as a key may be used, and the configuration value for the key will be a dictionary mapping from the key name to the value. In this case, the \attribute{attribute} attribute must be specified, and the data type for the key will be applied to each key which is found. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the key. If the value is \code{yes}, no \element{default} elements may be specified and an error will be reported if the configuration instance does not specify at least one value for the key. If the value is \code{no} (the default) and the configuration instance does not specify a value, the value reported to the application will be a list containing one element for each \element{default} element specified as a child of the \element{multikey}. Each value will be individually converted according to the \attribute{datatype} attribute. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{default}{PCDATA} Each \element{default} element specifies a single default value for a \element{multikey}. This element can be repeated to produce a list of individual default values. The text contained in the element will be passed to the datatype conversion for the \element{multikey}. \begin{attributedesc}{key}{key type of the containing sectiontype} Key to associate with the default value. This is only used for defaults of a \element{key} or \element{multikey} with a \attribute{name} of \code{+}; in that case this attribute is required. It is an error to use the \attribute{key} attribute with a \element{default} element for a \element{multikey} with a name other than \code{+}. \begin{notice}[warning] The datatype of this attribute is that of the section type \emph{containing} the actual keys, not necessarily that of the section type which defines the key. If a derived section overrides the key type of the base section type, the actual key type used is that of the derived section. This can lead to confusing errors in schemas, though the \refmodule{ZConfig} package checks for this when the schema is loaded. This situation is particularly likely when a derived section type uses a key type which collapses multiple default keys which were not collapsed by the base section type. Consider this example schema: \begin{verbatim} some value some value
\end{verbatim} When this schema is loaded, a set of defaults for the \datatype{derived} section type is computed. Since \datatype{basic-key} is case-insensitive (everything is converted to lower case), \samp{foo} and \samp{Foo} are both converted to \samp{foo}, which clashes since \element{key} only allows one value for each key. \end{notice} \end{attributedesc} \end{elementdesc} \begin{elementdesc}{section}{description?} A \element{section} element is used to describe a section which may occur at most once in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this section should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the section name to underscores, in which case the \attribute{name} attribute may not be \code{*} or \code{+}. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the section, as it must be given in a configuration instance, \code{*}, or \code{+}. If the value is \code{*} or this attribute is omitted, any name not already specified as a key may be used. If the value is \code{*} or \code{+}, the \attribute{attribute} attribute must be specified. If the value is \code{*}, any name is allowed, or the name may be omitted. If the value is \code{+}, any name is allowed, but some name must be provided. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the section. If the value is \code{yes}, an error will be reported if the configuration instance does not include the section. If the value is \code{no} (the default) and the configuration instance does not include the section, the value reported to the application will be \code{None}. \end{attributedesc} \begin{attributedesc}{type}{\datatype{basic-key}} The section type which matching sections must implement. If the value names an abstract section type, matching sections in the configuration file must be of a type which specifies that it implements the named abstract type. If the name identifies a concrete type, the section type must match exactly. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{multisection}{description?} A \element{multisection} element is used to describe a section which may occur any number of times in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which matching sections should be the value of on a \class{SectionValue} instance. This is required and must be unique within the immediate contents of a section type or schema. The \class{SectionValue} instance will contain a list of matching sections. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} For a \element{multisection}, any name not already specified as a key may be used. If the value is \code{*} or \code{+}, the \attribute{attribute} attribute must be specified. If the value is \code{*} or this attribute is omitted, any name is allowed, or the name may be omitted. If the value is \code{+}, any name is allowed, but some name must be provided. No other value for the \attribute{name} attribute is allowed for a \element{multisection}. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide at least one matching section. If the value is \code{yes}, an error will be reported if the configuration instance does not include the section. If the value is \code{no} (the default) and the configuration instance does not include the section, the value reported to the application will be \code{None}. \end{attributedesc} \begin{attributedesc}{type}{\datatype{basic-key}} The section type which matching sections must implement. If the value names an abstract section type, matching sections in the configuration file must be of types which specify that they implement the named abstract type. If the name identifies a concrete type, the section type must match exactly. \end{attributedesc} \end{elementdesc} \subsection{Schema Components \label{schema-components}} XXX need more explanation \module{ZConfig} supports schema components that can be provided by disparate components, and allows them to be knit together into concrete schema for applications. Components cannot add additional keys or sections in the application schema. A schema \dfn{component} is allowed to define new abstract and section types. Components are identified using a dotted-name, similar to a Python module name. For example, one component may be \code{zodb.storage}. Schema components are stored alongside application code since they directly reference datatype code. Schema components are provided by Python packages. The component definition is normally stored in the file \file{component.xml}; an alternate filename may be specified using the \attribute{file} attribute of the \element{import} element. Components imported using the \keyword{\%import} keyword from a configuration file must be named \file{component.xml}. The component defines the types provided by that component; it must have a \element{component} element as the document element. The following element is used as the document element for schema components. Note that schema components do not allow keys and sections to be added to the top-level of a schema; they serve only to provide type definitions. \begin{elementdesc}{component}{description?, (abstracttype | sectiontype)*} The top-level element for schema components. \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts within the \element{component} element if it hasn't been overridden by an inner element with a \attribute{prefix} attribute. \end{attributedesc} \end{elementdesc} \subsection{Referring to Files in Packages} The \attribute{extends} attribute of the \element{schema} element is used to refer to files containing base schema; sometimes it makes sense to refer to a base schema relative to the Python package that provides it. For this purpose, ZConfig supports the special \code{package:} URL scheme. The \code{package:} URL scheme is straightforward, and contains three parts: the scheme name, the package name, and a relative path. The relative path is searched for using the named package's \code{__path__} if it's a conventional filesystem package, or using the package's loader if that supports resource access (such as the loader for eggs and other ZIP-file based packages). The basic form of the \code{package:} URL is \begin{alltt} package:\var{package.name}:\var{relative-path} \end{alltt} The package name must be fully specified; the current prefix, if any, is not used. If the named package is contained in an egg or ZIP file, the resource identified by the relative path must reside in the same egg or ZIP file. The \code{package:} URL scheme is generally available everywhere ZConfig supports loading text from URLs directly, but applications using ZConfig do not automatically acquire general support for this. \section{Standard \module{ZConfig} Datatypes\label{standard-datatypes}} There are a number of data types which can be identified using the \attribute{datatype} attribute on \element{key}, \element{sectiontype}, and \element{schema} elements. Applications may extend the set of datatypes by calling the \method{register()} method of the data type registry being used or by using Python dotted-names to refer to conversion routines defined in code. The following data types are provided by the default type registry. \begin{definitions} \term{\datatype{basic-key}} The default data type for a key in a ZConfig configuration file. The result of conversion is always lower-case, and matches the regular expression \regexp{[a-z][-._a-z0-9]*}. \term{\datatype{boolean}} Convert a human-friendly string to a boolean value. The names \code{yes}, \code{on}, and \code{true} convert to \constant{True}, while \code{no}, \code{off}, and \code{false} convert to \constant{False}. Comparisons are case-insensitive. All other input strings are disallowed. \term{\datatype{byte-size}} A specification of a size, with byte multiplier suffixes (for example, \samp{128MB}). Suffixes are case insensitive and may be \samp{KB}, \samp{MB}, or \samp{GB} \term{\datatype{dotted-name}} A string consisting of one or more \datatype{identifier} values separated by periods (\character{.}). \term{\datatype{dotted-suffix}} A string consisting of one or more \datatype{identifier} values separated by periods (\character{.}), possibly prefixed by a period. This can be used to indicate a dotted name that may be specified relative to some base dotted name. \term{\datatype{existing-dirpath}} Validates that the directory portion of a pathname exists. For example, if the value provided is \file{/foo/bar}, \file{/foo} must be an existing directory. No conversion is performed. \term{\datatype{existing-directory}} Validates that a directory by the given name exists on the local filesystem. No conversion is performed. \term{\datatype{existing-file}} Validates that a file by the given name exists. No conversion is performed. \term{\datatype{existing-path}} Validates that a path (file, directory, or symlink) by the given name exists on the local filesystem. No conversion is performed. \term{\datatype{float}} A Python float. \code{Inf}, \code{-Inf}, and \code{NaN} are not allowed. \term{\datatype{identifier}} Any valid Python identifier. \term{\datatype{inet-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. If only the port is specified, the default host will be returned for \var{hostname}. The default host is \code{localhost} on Windows and the empty string on all other platforms. If the port is omitted, \code{None} will be returned for \var{port}. IPv6 addresses can be specified in colon-separated notation; if both host and port need to be specified, the bracketed form (\code{[addr]:port}) must be used. \term{\datatype{inet-binding-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. The address is suitable for binding a socket. If only the port is specified, the default host will be returned for \var{hostname}. The default host is the empty string on all platforms. If the port is omitted, \code{None} will be returned for \var{port}. \term{\datatype{inet-connection-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. The address is suitable for connecting a socket to a server. If only the port is specified, \code{'127.0.0.1'} will be returned for \var{hostname}. If the port is omitted, \code{None} will be returned for \var{port}. \term{\datatype{integer}} Convert a value to an integer. This will be a Python \class{int} if the value is in the range allowed by \class{int}, otherwise a Python \class{long} is returned. \term{\datatype{ipaddr-or-hostname}} Validates a valid IP address or hostname. If the first character is a digit, the value is assumed to be an IP address. If the first character is not a digit, the value is assumed to be a hostname. Strings containing colons are considered IPv6 address. Hostnames are converted to lower case. \term{\datatype{locale}} Any valid locale specifier accepted by the available \function{locale.setlocale()} function. Be aware that only the \code{'C'} locale is supported on some platforms. \term{\datatype{null}} No conversion is performed; the value passed in is the value returned. This is the default data type for section values. \term{\datatype{port-number}} Returns a valid port number as an integer. Validity does not imply that any particular use may be made of the port, however. For example, port number lower than 1024 generally cannot be bound by non-root users. \term{\datatype{socket-address}} An address for a socket. The converted value is an object providing two attributes. \member{family} specifies the address family (\constant{AF_INET} or \constant{AF_UNIX}), with \code{None} instead of \constant{AF_UNIX} on platforms that don't support it. The \member{address} attribute will be the address that should be passed to the socket's \method{bind()} method. If the family is \constant{AF_UNIX}, the specific address will be a pathname; if the family is \constant{AF_INET}, the second part will be the result of the \datatype{inet-address} conversion. \term{\datatype{string}} Returns the input value as a string. If the source is a Unicode string, this implies that it will be checked to be simple 7-bit \ASCII. This is the default data type for values in configuration files. \term{\datatype{time-interval}} A specification of a time interval in seconds, with multiplier suffixes (for example, \code{12h}). Suffixes are case insensitive and may be \samp{s} (seconds), \samp{m} (minutes), \samp{h} (hours), or \samp{d} (days). \term{\datatype{timedelta}} Similar to the \datatype{time-interval}, this data type returns a Python datetime.timedelta object instead of a float. The set of suffixes recognized by \datatype{timedelta} are: \samp{w} (weeks), \samp{d} (days), \samp{h} (hours), \samp{m} (minutes), \samp{s} (seconds). Values may be floats, for example: \code{4w 2.5d 7h 12m 0.001s}. \end{definitions} \section{Standard \module{ZConfig} Schema Components \label{standard-components}} \module{ZConfig} provides a few convenient schema components as part of the package. These may be used directly or can server as examples for creating new components. \subsection{\module{ZConfig.components.basic}} The \module{ZConfig.components.basic} package provides small components that can be helpful in composing application-specific components and schema. There is no large functionality represented by this package. The default component provided by this package simply imports all of the smaller components. This can be imported using \begin{verbatim} \end{verbatim} Each of the smaller components is documented directly; importing these selectively can reduce the time it takes to load a schema slightly, and allows replacing the other basic components with alternate components (by using different imports that define the same type names) if desired. \subsubsection{The Mapping Section Type \label{basic-mapping}} There is a basic section type that behaves like a simple Python mapping; this can be imported directly using \begin{verbatim} \end{verbatim} This defines a single section type, \datatype{ZConfig.basic.mapping}. When this is used, the section value is a Python dictionary mapping keys to string values. This type is intended to be used by extending it in simple ways. The simplest is to create a new section type name that makes more sense for the application: \begin{verbatim}
\end{verbatim} This allows a configuration to contain a mapping from \datatype{basic-key} names to string values like this: \begin{verbatim} This that and the other \end{verbatim} The value of the configuration object's \member{map} attribute would then be the dictionary \begin{verbatim} {'this': 'that', 'and': 'the other', } \end{verbatim} (Recall that the \datatype{basic-key} data type converts everything to lower case.) Perhaps a more interesting application of \datatype{ZConfig.basic.mapping} is using the derived type to override the \attribute{keytype}. If we have the conversion function: \begin{verbatim} def email_address(value): userid, hostname = value.split("@", 1) hostname = hostname.lower() # normalize what we know we can return "%s@%s" % (userid, hostname) \end{verbatim} then we can use this as the key type for a derived mapping type: \begin{verbatim}
\end{verbatim} \subsection{\module{ZConfig.components.logger}} The \module{ZConfig.components.logger} package provides configuration support for the \ulink{\module{logging} package} {http://docs.python.org/library/logging.html} in Python's standard library. This component can be imported using \begin{verbatim} \end{verbatim} This component defines two abstract types and several concrete section types. These can be imported as a unit, as above, or as four smaller components usable in creating alternate logging packages. The first of the four smaller components contains the abstract types, and can be imported using \begin{verbatim} \end{verbatim} The two abstract types imported by this are: \begin{definitions} \term{\datatype{ZConfig.logger.log}} Logger objects are represented by this abstract type. \term{\datatype{ZConfig.logger.handler}} Each logger object can have one or more ``handlers'' associated with them. These handlers are responsible for writing logging events to some form of output stream using appropriate formatting. The output stream may be a file on a disk, a socket communicating with a server on another system, or a series of \code{syslog} messages. Section types which implement this type represent these handlers. \end{definitions} The second and third of the smaller components provides section types that act as factories for \class{logging.Logger} objects. These can be imported using \begin{verbatim} \end{verbatim} The types defined in these components implement the \datatype{ZConfig.logger.log} abstract type. The \file{eventlog.xml} component defines an \datatype{eventlog} type which represents the root logger from the the \module{logging} package (the return value of \function{logging.getLogger()}), while the \file{logger.xml} component defines a \datatype{logger} section type which represents a named logger (as returned by \function{logging.getLogger(\var{name})}). The third of the smaller components provides section types that are factories for \class{logging.Handler} objects. This can be imported using \begin{verbatim} \end{verbatim} The types defined in this component implement the \datatype{ZConfig.logger.handler} abstract type. The configuration objects provided by both the logger and handler types are factories for the finished loggers and handlers. These factories should be called with no arguments to retrieve the logger or log handler objects. Calling the factories repeatedly will cause the same objects to be returned each time, so it's safe to simply call them to retrieve the objects. The factories for the logger objects, whether the \datatype{eventlog} or \datatype{logger} section type is used, provide a \method{reopen()} method which may be called to close any log files and re-open them. This is useful when using a \UNIX{} signal to effect log file rotation: the signal handler can call this method, and not have to worry about what handlers have been registered for the logger. There is also a function in the \module{ZConfig.components.logger.loghandler} module that re-opens all open log files created using ZConfig configuraiton: \begin{funcdesc}{reopenFiles}{} Closes and re-opens all the log files held open by handlers created by the factories for \code{logfile} sections. This is intended to help support log rotation for applications. \end{funcdesc} Building an application that uses the logging components is fairly straightforward. The schema needs to import the relevant components and declare their use: \begin{verbatim}
\end{verbatim} In the application, the schema and configuration file should be loaded normally. Once the configuration object is available, the logger factory should be called to configure Python's \module{logging} package: \begin{verbatim} import os import ZConfig def run(configfile): schemafile = os.path.join(os.path.dirname(__file__), "schema.xml") schema = ZConfig.loadSchema(schemafile) config, handlers = ZConfig.loadConfig(schema, configfile) # configure the logging package: config.eventlog() # now do interesting things \end{verbatim} An example configuration file for this application may look like this: \begin{verbatim} level info path /var/log/myapp format %(asctime)s %(levelname)s %(name)s %(message)s # locale-specific date/time representation dateformat %c level error address syslog.example.net:514 format %(levelname)s %(name)s %(message)s \end{verbatim} Refer to the \module{logging} package documentation for the names available in the message format strings (the \code{format} key in the log handlers). The date format strings (the \code{dateformat} key in the log handlers) are the same as those accepted by the \function{time.strftime()} function. \subsubsection{Configuring the email logger} ZConfig has support for Python's STMTPHandler via the handler. \begin{verbatim} to sysadmin@example.com to john@example.com from zlog-user@example.com level fatal smtp-username john smtp-password johnpw \end{verbatim} For details about the SMTPHandler see the Python \module{logging} module. \begin{seealso} \seepep{282}{A Logging System} {The proposal which described the logging feature for inclusion in the Python standard library.} \seelink{http://docs.python.org/library/logging.html} {\module{logging} --- Logging facility for Python} {Python's \module{logging} package documentation, from the \citetitle[http://docs.python.org/library/] {Python Library Reference}.} \seelink{http://www.red-dove.com/python_logging.html} {Original Python \module{logging} package} {This is the original source for the \module{logging} package. This is mostly of historical interest.} \end{seealso} \section{Using Components to Extend Schema} % XXX This section needs a lot of work, but should get people started % who really want to add new pieces to ZConfig-configured applications. It is possible to use schema components and the \keyword{\%import} construct to extend the set of section types available for a specific configuration file, and allow the new components to be used in place of standard components. The key to making this work is the use of abstract section types. Wherever the original schema accepts an abstract type, it is possible to load new implementations of the abstract type and use those instead of, or in addition to, the implementations loaded by the original schema. Abstract types are generally used to represent interfaces. Sometimes these are interfaces for factory objects, and sometimes not, but there's an interface that the new component needs to implement. What interface is required should be documented in the \element{description} element in the \element{abstracttype} element; this may be by reference to an interface specified in a Python module or described in some other bit of documentation. The following things need to be created to make the new component usable from the configuration file: \begin{enumerate} \item An implementation of the required interface. \item A schema component that defines a section type that contains the information needed to construct the component. \item A ``datatype'' function that converts configuration data to an instance of the component. \end{enumerate} For simplicity, let's assume that the implementation is defined by a Python class. The example component we build here will be in the \module{noise} package, but any package will do. Components loadable using \keyword{\%import} must be contained in the \file{component.xml} file; alternate filenames may not be selected by the \keyword{\%import} construct. Create a ZConfig component that provides a section type to support your component. The new section type must declare that it implements the appropriate abstract type; it should probably look something like this: \begin{verbatim} Port number to listen on. Silly way to specify a noise generation algorithm. \end{verbatim} This example uses one of the standard ZConfig datatypes, \datatype{port-number}, and requires two additional types to be provided by the \module{noise.server} module: \class{NoiseServerFactory} and \function{noise_color()}. The \function{noise_color()} function is a datatype conversion for a key, so it accepts a string and returns the value that should be used: \begin{verbatim} _noise_colors = { # color -> r,g,b 'white': (255, 255, 255), 'pink': (255, 182, 193), } def noise_color(string): if string in _noise_colors: return _noise_colors[string] else: raise ValueError('unknown noise color: %r' % string) \end{verbatim} \class{NoiseServerFactory} is a little different, as it's the datatype function for a section rather than a key. The parameter isn't a string, but a section value object with two attributes, \member{port} and \member{color}. Since the \datatype{ZServer.server} abstract type requires that the component returned is a factory object, the datatype function can be implemented at the constructor for the class of the factory object. (If the datatype function could select different implementation classes based on the configuration values, it makes more sense to use a simple function that returns the appropriate implementation.) A class that implements this datatype might look like this: \begin{verbatim} from ZServer.datatypes import ServerFactory from noise.generator import WhiteNoiseGenerator, PinkNoiseGenerator class NoiseServerFactory(ServerFactory): def __init__(self, section): # host and ip will be initialized by ServerFactory.prepare() self.host = None self.ip = None self.port = section.port self.color = section.color def create(self): if self.color == 'white': generator = WhiteNoiseGenerator() else: generator = PinkNoiseGenerator() return NoiseServer(self.ip, self.port, generator) \end{verbatim} You'll need to arrange for the package containing this component to be available on Python's \code{sys.path} before the configuration file is loaded; this is mostly easily done by manipulating the \envvar{PYTHONPATH} environment variable. Your configuration file can now include the following to load and use your new component: \begin{verbatim} %import noise port 1234 color white \end{verbatim} \section{\module{ZConfig} --- Basic configuration support} \declaremodule{}{ZConfig} \modulesynopsis{Configuration package.} The main \module{ZConfig} package exports these convenience functions: \begin{funcdesc}{loadConfig}{schema, url\optional{, overrides}} Load and return a configuration from a URL or pathname given by \var{url}. \var{url} may be a URL, absolute pathname, or relative pathname. Fragment identifiers are not supported. \var{schema} is a reference to a schema loaded by \function{loadSchema()} or \function{loadSchemaFile()}. The return value is a tuple containing the configuration object and a composite handler that, when called with a name-to-handler mapping, calls all the handlers for the configuration. The optional \var{overrides} argument represents information derived from command-line arguments. If given, it must be either a sequence of value specifiers, or \code{None}. A \dfn{value specifier} is a string of the form \code{\var{optionpath}=\var{value}}. The \var{optionpath} specifies the ``full path'' to the configuration setting: it can contain a sequence of names, separated by \character{/} characters. Each name before the last names a section from the configuration file, and the last name corresponds to a key within the section identified by the leading section names. If \var{optionpath} contains only one name, it identifies a key in the top-level schema. \var{value} is a string that will be treated just like a value in the configuration file. \end{funcdesc} \begin{funcdesc}{loadConfigFile}{schema, file\optional{, url\optional{, overrides}}} Load and return a configuration from an opened file object. If \var{url} is omitted, one will be computed based on the \member{name} attribute of \var{file}, if it exists. If no URL can be determined, all \keyword{\%include} statements in the configuration must use absolute URLs. \var{schema} is a reference to a schema loaded by \function{loadSchema()} or \function{loadSchemaFile()}. The return value is a tuple containing the configuration object and a composite handler that, when called with a name-to-handler mapping, calls all the handlers for the configuration. The \var{overrides} argument is the same as for the \function{loadConfig()} function. \end{funcdesc} \begin{funcdesc}{loadSchema}{url} Load a schema definition from the URL \var{url}. \var{url} may be a URL, absolute pathname, or relative pathname. Fragment identifiers are not supported. The resulting schema object can be passed to \function{loadConfig()} or \function{loadConfigFile()}. The schema object may be used as many times as needed. \end{funcdesc} \begin{funcdesc}{loadSchemaFile}{file\optional{, url}} Load a schema definition from the open file object \var{file}. If \var{url} is given and not \code{None}, it should be the URL of resource represented by \var{file}. If \var{url} is omitted or \code{None}, a URL may be computed from the \member{name} attribute of \var{file}, if present. The resulting schema object can be passed to \function{loadConfig()} or \function{loadConfigFile()}. The schema object may be used as many times as needed. \end{funcdesc} The following exceptions are defined by this package: \begin{excdesc}{ConfigurationError} Base class for exceptions specific to the \module{ZConfig} package. All instances provide a \member{message} attribute that describes the specific error, and a \member{url} attribute that gives the URL of the resource the error was located in, or \constant{None}. \end{excdesc} \begin{excdesc}{ConfigurationSyntaxError} Exception raised when a configuration source does not conform to the allowed syntax. In addition to the \member{message} and \member{url} attributes, exceptions of this type offer the \member{lineno} attribute, which provides the line number at which the error was detected. \end{excdesc} \begin{excdesc}{DataConversionError} Raised when a data type conversion fails with \exception{ValueError}. This exception is a subclass of both \exception{ConfigurationError} and \exception{ValueError}. The \function{str()} of the exception provides the explanation from the original \exception{ValueError}, and the line number and URL of the value which provoked the error. The following additional attributes are provided: \begin{tableii}{l|l}{member}{Attribute}{Value} \lineii{colno} {column number at which the value starts, or \code{None}} \lineii{exception} {the original \exception{ValueError} instance} \lineii{lineno} {line number on which the value starts} \lineii{message} {\function{str()} returned by the original \exception{ValueError}} \lineii{value} {original value passed to the conversion function} \lineii{url} {URL of the resource providing the value text} \end{tableii} \end{excdesc} \begin{excdesc}{SchemaError} Raised when a schema contains an error. This exception type provides the attributes \member{url}, \member{lineno}, and \member{colno}, which provide the source URL, the line number, and the column number at which the error was detected. These attributes may be \code{None} in some cases. \end{excdesc} \begin{excdesc}{SchemaResourceError} Raised when there's an error locating a resource required by the schema. This is derived from \exception{SchemaError}. Instances of this exception class add the attributes \member{filename}, \member{package}, and \member{path}, which hold the filename searched for within the package being loaded, the name of the package, and the \code{__path__} attribute of the package itself (or \constant{None} if it isn't a package or could not be imported). \end{excdesc} \begin{excdesc}{SubstitutionReplacementError} Raised when the source text contains references to names which are not defined in \var{mapping}. The attributes \member{source} and \member{name} provide the complete source text and the name (converted to lower case) for which no replacement is defined. \end{excdesc} \begin{excdesc}{SubstitutionSyntaxError} Raised when the source text contains syntactical errors. \end{excdesc} \subsection{Basic Usage} The simplest use of \refmodule{ZConfig} is to load a configuration based on a schema stored in a file. This example loads a configuration file specified on the command line using a schema in the same directory as the script: \begin{verbatim} import os import sys import ZConfig try: myfile = __file__ except NameError: myfile = os.path.realpath(sys.argv[0]) mydir = os.path.dirname(myfile) schema = ZConfig.loadSchema(os.path.join(mydir, 'schema.xml')) conf, handler = ZConfig.loadConfig(schema, sys.argv[1]) \end{verbatim} If the schema file contained this schema: \begin{verbatim} \end{verbatim} and the file specified on the command line contained this text: \begin{verbatim} # sample configuration server www.example.com \end{verbatim} then the configuration object \code{conf} loaded above would have two attributes: \begin{tableii}{l|l}{member}{Attribute}{Value} \lineii{server}{\code{'www.example.com'}} \lineii{attempts}{\code{5}} \end{tableii} \section{\module{ZConfig.datatypes} --- Default data type registry} \declaremodule{}{ZConfig.datatypes} \modulesynopsis{Default implementation of a data type registry} The \module{ZConfig.datatypes} module provides the implementation of the default data type registry and all the standard data types supported by \module{ZConfig}. A number of convenience classes are also provided to assist in the creation of additional data types. A \dfn{datatype registry} is an object that provides conversion functions for data types. The interface for a registry is fairly simple. A \dfn{conversion function} is any callable object that accepts a single argument and returns a suitable value, or raises an exception if the input value is not acceptable. \exception{ValueError} is the preferred exception for disallowed inputs, but any other exception will be properly propagated. \begin{classdesc}{Registry}{\optional{stock}} Implementation of a simple type registry. If given, \var{stock} should be a mapping which defines the ``built-in'' data types for the registry; if omitted or \code{None}, the standard set of data types is used (see section~\ref{standard-datatypes}, ``Standard \module{ZConfig} Datatypes''). \end{classdesc} \class{Registry} objects have the following methods: \begin{methoddesc}{get}{name} Return the type conversion routine for \var{name}. If the conversion function cannot be found, an (unspecified) exception is raised. If the name is not provided in the stock set of data types by this registry and has not otherwise been registered, this method uses the \method{search()} method to load the conversion function. This is the only method the rest of \module{ZConfig} requires. \end{methoddesc} \begin{methoddesc}{register}{name, conversion} Register the data type name \var{name} to use the conversion function \var{conversion}. If \var{name} is already registered or provided as a stock data type, \exception{ValueError} is raised (this includes the case when \var{name} was found using the \method{search()} method). \end{methoddesc} \begin{methoddesc}{search}{name} This is a helper method for the default implementation of the \method{get()} method. If \var{name} is a Python dotted-name, this method loads the value for the name by dynamically importing the containing module and extracting the value of the name. The name must refer to a usable conversion function. \end{methoddesc} The following classes are provided to define conversion functions: \begin{classdesc}{MemoizedConversion}{conversion} Simple memoization for potentially expensive conversions. This conversion helper caches each successful conversion for re-use at a later time; failed conversions are not cached in any way, since it is difficult to raise a meaningful exception providing information about the specific failure. \end{classdesc} \begin{classdesc}{RangeCheckedConversion}{conversion\optional{, min\optional{, max}}} Helper that performs range checks on the result of another conversion. Values passed to instances of this conversion are converted using \var{conversion} and then range checked. \var{min} and \var{max}, if given and not \code{None}, are the inclusive endpoints of the allowed range. Values returned by \var{conversion} which lay outside the range described by \var{min} and \var{max} cause \exception{ValueError} to be raised. \end{classdesc} \begin{classdesc}{RegularExpressionConversion}{regex} Conversion that checks that the input matches the regular expression \var{regex}. If it matches, returns the input, otherwise raises \exception{ValueError}. \end{classdesc} \section{\module{ZConfig.loader} --- Resource loading support} \declaremodule{}{ZConfig.loader} \modulesynopsis{Support classes for resource loading} This module provides some helper classes used by the primary APIs exported by the \module{ZConfig} package. These classes may be useful for some applications, especially applications that want to use a non-default data type registry. \begin{classdesc}{Resource}{file, url\optional{, fragment}} Object that allows an open file object and a URL to be bound together to ease handling. Instances have the attributes \member{file}, \member{url}, and \member{fragment} which store the constructor arguments. These objects also have a \method{close()} method which will call \method{close()} on \var{file}, then set the \member{file} attribute to \code{None} and the \member{closed} to \constant{True}. \end{classdesc} \begin{classdesc}{BaseLoader}{} Base class for loader objects. This should not be instantiated directly, as the \method{loadResource()} method must be overridden for the instance to be used via the public API. \end{classdesc} \begin{classdesc}{ConfigLoader}{schema} Loader for configuration files. Each configuration file must conform to the schema \var{schema}. The \method{load*()} methods return a tuple consisting of the configuration object and a composite handler. \end{classdesc} \begin{classdesc}{SchemaLoader}{\optional{registry}} Loader that loads schema instances. All schema loaded by a \class{SchemaLoader} will use the same data type registry. If \var{registry} is provided and not \code{None}, it will be used, otherwise an instance of \class{ZConfig.datatypes.Registry} will be used. \end{classdesc} \subsection{Loader Objects} Loader objects provide a general public interface, an interface which subclasses must implement, and some utility methods. The following methods provide the public interface: \begin{methoddesc}[loader]{loadURL}{url} Open and load a resource specified by the URL \var{url}. This method uses the \method{loadResource()} method to perform the actual load, and returns whatever that method returns. \end{methoddesc} \begin{methoddesc}[loader]{loadFile}{file\optional{, url}} Load from an open file object, \var{file}. If given and not \code{None}, \var{url} should be the URL of the resource represented by \var{file}. If omitted or \code{None}, the \member{name} attribute of \var{file} is used to compute a \code{file:} URL, if present. This method uses the \method{loadResource()} method to perform the actual load, and returns whatever that method returns. \end{methoddesc} The following method must be overridden by subclasses: \begin{methoddesc}[loader]{loadResource}{resource} Subclasses of \class{BaseLoader} must implement this method to actually load the resource and return the appropriate application-level object. \end{methoddesc} The following methods can be used as utilities: \begin{methoddesc}[loader]{isPath}{s} Return true if \var{s} should be considered a filesystem path rather than a URL. \end{methoddesc} \begin{methoddesc}[loader]{normalizeURL}{url-or-path} Return a URL for \var{url-or-path}. If \var{url-or-path} refers to an existing file, the corresponding \code{file:} URL is returned. Otherwise \var{url-or-path} is checked for sanity: if it does not have a schema, \exception{ValueError} is raised, and if it does have a fragment identifier, \exception{ConfigurationError} is raised. This uses \method{isPath()} to determine whether \var{url-or-path} is a URL of a filesystem path. \end{methoddesc} \begin{methoddesc}[loader]{openResource}{url} Returns a resource object that represents the URL \var{url}. The URL is opened using the \function{urllib2.urlopen()} function, and the returned resource object is created using \method{createResource()}. If the URL cannot be opened, \exception{ConfigurationError} is raised. \end{methoddesc} \begin{methoddesc}[loader]{createResource}{file, url} Returns a resource object for an open file and URL, given as \var{file} and \var{url}, respectively. This may be overridden by a subclass if an alternate resource implementation is desired. \end{methoddesc} \section{\module{ZConfig.cmdline} --- Command-line override support} \declaremodule{}{ZConfig.cmdline} \modulesynopsis{Support for command-line overrides for configuration settings.} This module exports an extended version of the \class{ConfigLoader} class from the \refmodule{ZConfig.loader} module. This provides support for overriding specific settings from the configuration file from the command line, without requiring the application to provide specific options for everything the configuration file can include. \begin{classdesc}{ExtendedConfigLoader}{schema} Construct a \class{ConfigLoader} subclass that adds support for command-line overrides. \end{classdesc} The following additional method is provided, and is the only way to provide position information to associate with command-line parameters: \begin{methoddesc}{addOption}{spec\optional{, pos}} Add a single value to the list of overridden values. The \var{spec} argument is a value specified, as described for the \function{\refmodule{ZConfig}.loadConfig()} function. A source position for the specifier may be given as \var{pos}. If \var{pos} is specified and not \code{None}, it must be a sequence of three values. The first is the URL of the source (or some other identifying string). The second and third are the line number and column of the setting. These position information is only used to construct a \exception{DataConversionError} when data conversion fails. \end{methoddesc} \section{\module{ZConfig.substitution} --- String substitution} \declaremodule{}{ZConfig.substitution} \modulesynopsis{Shell-style string substitution helper.} This module provides a basic substitution facility similar to that found in the Bourne shell (\program{sh} on most \UNIX{} platforms). The replacements supported by this module include: \begin{tableiii}{l|l|c}{code}{Source}{Replacement}{Notes} \lineiii{\$\$}{\code{\$}}{(1)} \lineiii{\$\var{name}}{The result of looking up \var{name}}{(2)} \lineiii{\$\{\var{name}\}}{The result of looking up \var{name}}{} \end{tableiii} \noindent Notes: \begin{description} \item[(1)] This is different from the Bourne shell, which uses \code{\textbackslash\$} to generate a \character{\$} in the result text. This difference avoids having as many special characters in the syntax. \item[(2)] Any character which immediately follows \var{name} may not be a valid character in a name. \end{description} In each case, \var{name} is a non-empty sequence of alphanumeric and underscore characters not starting with a digit. If there is not a replacement for \var{name}, the exception \exception{SubstitutionReplacementError} is raised. Note that the lookup is expected to be case-insensitive; this module will always use a lower-case version of the name to perform the query. This module provides these functions: \begin{funcdesc}{substitute}{s, mapping} Substitute values from \var{mapping} into \var{s}. \var{mapping} can be a \class{dict} or any type that supports the \method{get()} method of the mapping protocol. Replacement values are copied into the result without further interpretation. Raises \exception{SubstitutionSyntaxError} if there are malformed constructs in \var{s}. \end{funcdesc} \begin{funcdesc}{isname}{s} Returns \constant{True} if \var{s} is a valid name for a substitution text, otherwise returns \constant{False}. \end{funcdesc} \subsection{Examples} \begin{verbatim} >>> from ZConfig.substitution import substitute >>> d = {'name': 'value', ... 'top': '$middle', ... 'middle' : 'bottom'} >>> >>> substitute('$name', d) 'value' >>> substitute('$top', d) '$middle' \end{verbatim} \appendix \section{Schema Document Type Definition \label{schema-dtd}} The following is the XML Document Type Definition for \module{ZConfig} schema: \verbatiminput{schema.dtd} \end{document} ZConfig-3.1.0/LICENSE.txt0000644000076500000240000000402612610530667015023 0ustar do3ccstaff00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. ZConfig-3.1.0/MANIFEST.in0000644000076500000240000000024112610530667014731 0ustar do3ccstaff00000000000000include *.rst include *.txt include tox.ini include bootstrap.py include buildout.cfg recursive-include ZConfig * recursive-include doc * global-exclude *.pyc ZConfig-3.1.0/PKG-INFO0000644000076500000240000003450212610530670014271 0ustar do3ccstaff00000000000000Metadata-Version: 1.1 Name: ZConfig Version: 3.1.0 Summary: Structured Configuration Library Home-page: http://www.zope.org/Members/fdrake/zconfig/ Author: Zope Foundation and Contributors Author-email: fred@zope.com License: ZPL 2.1 Description: ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ========================== Change History for ZConfig ========================== 3.1.0 (2015-10-17) ------------------ - Add ability to do variable substitution from environment variables using $() syntax. 3.0.4 (2014-03-20) ------------------ - Added Python 3.4 support. 3.0.3 (2013-03-02) ------------------ - Added Python 3.2 support. 3.0.2 (2013-02-14) ------------------ - Fixed ResourceWarning in BaseLoader.openResource(). 3.0.1 (2013-02-13) ------------------ - Removed an accidentally left `pdb` statement from the code. - Fix a bug in Python 3 with the custom string `repr()` function. 3.0.0 (2013-02-13) ------------------ - Added Python 3.3 support. - Dropped Python 2.4 and 2.5 support. 2.9.3 (2012-06-25) ------------------ - Fixed: port values of 0 weren't allowed. Port 0 is used to request an ephemeral port. 2.9.2 (2012-02-11) ------------------ - Adjust test classes to avoid base classes being considered separate test cases by (at least) the "nose" test runner. 2.9.1 (2012-02-11) ------------------ - Make FileHandler.reopen thread safe. 2.9.0 (2011-03-22) ------------------ - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. 2.8.0 (2010-04-13) ------------------ - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. 2.7.1 (2009-06-13) ------------------ - Improved documentation - Fixed tests failures on windows. 2.7.0 (2009-06-11) ------------------ - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. 2.6.1 (2008-12-05) ------------------ - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. 2.6.0 (2008-09-03) ------------------ - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. 2.5.1 (2007-12-24) ------------------ - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. 2.3.1 (2005-08-21) ------------------ - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. 2.3 (2005-05-18) ---------------- - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. 2.2 (2004-04-21) ---------------- - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. 2.1 (2004-04-12) ---------------- - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. 2.0 (2003-10-27) ---------------- - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. 1.0 (2003-03-25) ---------------- - Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development ZConfig-3.1.0/README.txt0000644000076500000240000000606112610530667014677 0ustar do3ccstaff00000000000000ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ZConfig-3.1.0/scripts/0000755000076500000240000000000012610530670014657 5ustar do3ccstaff00000000000000ZConfig-3.1.0/scripts/zconfig0000755000076500000240000000540512610530667016256 0ustar do3ccstaff00000000000000#!/usr/bin/env python ############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zconfig: Script to check validity of a configuration file. Usage: zconfig [options] [file...] Options: -h --help Print this help text. -s file --schema file Use the schema in 'file' to validate the configuration; this must be specified. Each file named on the command line is checked for syntactical errors and schema conformance. The schema must be specified. If no files are specified and standard input is not a TTY, standard in is treated as a configuration file. Specifying a schema and no configuration files causes the schema to be checked. """ import optparse import sys import os if __name__ == "__main__": here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("ZConfig",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break import ZConfig def main(): optparser = optparse.OptionParser( usage="usage: %prog [-s FILE] [file ...]") optparser.add_option( "-s", "--schema", dest="schema", help="use the schema in FILE (can be a URL)", metavar="FILE") options, args = optparser.parse_args() if not options.schema: print >>sys.stderr, "No schema specified, but is required." usage(sys.stderr) return 2 schema = ZConfig.loadSchema(options.schema) if not args: if sys.stdin.isatty(): # just checking the schema return 0 else: # stdin is a pipe args = ["-"] errors = 0 for fn in args: try: if fn == "-": ZConfig.loadConfigFile(schema, sys.stdin) else: ZConfig.loadConfig(schema, fn) except ZConfig.ConfigurationError, e: print >>sys.stderr, str(e) errors += 1 if errors: return 1 else: return 0 def usage(fp): print >>fp, __doc__ if __name__ == "__main__": sys.exit(main()) ZConfig-3.1.0/scripts/zconfig_schema2html0000755000076500000240000000675512610530667020556 0ustar do3ccstaff00000000000000#!/usr/bin/env python ############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## __version__ = '$Revision: 1.3 $'[11:-2] import sys import os import cgi if __name__ == "__main__": here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("ZConfig",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break import ZConfig.loader from ZConfig.info import * def esc(x): return cgi.escape(str(x)) def dt(x): tn = type(x).__name__ if tn == 'instance': return '%s %s'%(tn, x.__class__.__module__ + '.' + x.__class__.__name__) elif tn == 'class': return '%s %s'%(tn, x.__module__ + '.' + x.__name__) else: return '%s %s'%(tn, x.__name__) class explain: done = [] def __call__(self, st): if st.name in self.done: return self.done.append(st.name) if st.description: print st.description for sub in st.getsubtypenames(): print '
' printContents(None, st.getsubtype(sub)) print '
' explain = explain() def printSchema(schema): print '
' for child in schema: printContents(*child) print '
' def printContents(name, info): if isinstance(info, SectionType): print '
', info.name, ' (%s)
'%dt(info.datatype) print '
' if info.description: print info.description print '
' for sub in info: printContents(*sub) print '
' elif isinstance(info, SectionInfo): st = info.sectiontype if st.isabstract(): print '
', st.name, '', info.name, '
' print '
' if info.description: print info.description explain(st) print '
' else: print '
', info.attribute, info.name, '' print '(%s)
'%dt(info.datatype) print '
' for sub in info.sectiontype: printContents(*sub) print '
' else: print '
',info.name, '', '(%s)'%dt(info.datatype) default = info.getdefault() if isinstance(default, ValueInfo): print '(default: %r)'%esc(default.value) elif default is not None: print '(default: %r)'%esc(default) if info.metadefault: print '(metadefault: %s)' % info.metadefault print '
' if info.description: print '
',info.description,'
' schema = ZConfig.loader.loadSchemaFile(sys.argv[1]) print ''' ''' printSchema(schema) print '' # vim: set filetype=python ts=4 sw=4 et si ZConfig-3.1.0/setup.cfg0000644000076500000240000000023412610530670015010 0ustar do3ccstaff00000000000000[bdist_rpm] doc_files = LICENSE.txt CHANGES.txt README.txt doc/schema.dtd doc/zconfig.pdf [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 ZConfig-3.1.0/setup.py0000644000076500000240000000504612610530667014715 0ustar do3ccstaff00000000000000README = open("README.txt").read() CHANGES = open("CHANGES.txt").read() def alltests(): import os import sys import unittest # use the zope.testrunner machinery to find all the # test suites we've put under ourselves import zope.testrunner.find import zope.testrunner.options here = os.path.abspath(os.path.dirname(sys.argv[0])) args = sys.argv[:] defaults = ["--test-path", here] options = zope.testrunner.options.get_options(args, defaults) suites = list(zope.testrunner.find.find_suites(options)) return unittest.TestSuite(suites) options = dict( name="ZConfig", version='3.1.0', author="Fred L. Drake, Jr.", author_email="fred@zope.com", maintainer="Zope Foundation and Contributors", description="Structured Configuration Library", long_description=README + "\n\n" + CHANGES, license="ZPL 2.1", url="http://www.zope.org/Members/fdrake/zconfig/", # List packages explicitly so we don't have to assume setuptools: packages=[ "ZConfig", "ZConfig.components", "ZConfig.components.basic", "ZConfig.components.basic.tests", "ZConfig.components.logger", "ZConfig.components.logger.tests", "ZConfig.tests", "ZConfig.tests.library", "ZConfig.tests.library.thing", "ZConfig.tests.library.widget", ], scripts=["scripts/zconfig", "scripts/zconfig_schema2html"], include_package_data=True, zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Zope Public License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: Implementation :: CPython', 'Operating System :: OS Independent', 'Topic :: Software Development', ], # Support for 'setup.py test' when setuptools is available: test_suite='__main__.alltests', tests_require=[ 'zope.testrunner', ], ) try: from setuptools import setup except ImportError: from distutils.core import setup setup(**options) ZConfig-3.1.0/tox.ini0000644000076500000240000000112312610530667014506 0ustar do3ccstaff00000000000000[tox] envlist = py26,py27,py32,py33,py34,py35 [testenv] commands = python setup.py test -q # without explicit deps, setup.py test will download a bunch of eggs into $PWD deps = zope.testrunner [testenv:coverage] basepython = python2.7 commands = # The installed version messes up nose's test discovery / coverage reporting # So, we uninstall that from the environment, and then install the editable # version, before running nosetests. pip uninstall -y ZConfig pip install -e . nosetests --with-xunit --with-xcoverage deps = nose coverage nosexcover ZConfig-3.1.0/ZConfig/0000755000076500000240000000000012610530670014527 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/__init__.py0000644000076500000240000001370612610530667016655 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Structured, schema-driven configuration library. ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to ``import`` schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. $Id: __init__.py,v 1.18 2004/04/15 20:33:32 fdrake Exp $ """ __docformat__ = "reStructuredText" version_info = (3, 0) __version__ = ".".join([str(n) for n in version_info]) from ZConfig.loader import loadConfig, loadConfigFile from ZConfig.loader import loadSchema, loadSchemaFile try: import StringIO as StringIO except ImportError: # Python 3 support. import io as StringIO class ConfigurationError(Exception): """Base class for ZConfig exceptions.""" # The 'message' attribute was deprecated for BaseException with # Python 2.6; here we create descriptor properties to continue using it def __set_message(self, v): self.__dict__['message'] = v def __get_message(self): return self.__dict__['message'] def __del_message(self): del self.__dict__['message'] message = property(__get_message, __set_message, __del_message) def __init__(self, msg, url=None): self.message = msg self.url = url Exception.__init__(self, msg) def __str__(self): return self.message class _ParseError(ConfigurationError): def __init__(self, msg, url, lineno, colno=None): self.lineno = lineno self.colno = colno ConfigurationError.__init__(self, msg, url) def __str__(self): s = self.message if self.url: s += "\n(" elif (self.lineno, self.colno) != (None, None): s += " (" if self.lineno: s += "line %d" % self.lineno if self.colno is not None: s += ", column %d" % self.colno if self.url: s += " in %s)" % self.url else: s += ")" elif self.url: s += self.url + ")" return s class SchemaError(_ParseError): """Raised when there's an error in the schema itself.""" def __init__(self, msg, url=None, lineno=None, colno=None): _ParseError.__init__(self, msg, url, lineno, colno) class SchemaResourceError(SchemaError): """Raised when there's an error locating a resource required by the schema. """ def __init__(self, msg, url=None, lineno=None, colno=None, path=None, package=None, filename=None): self.filename = filename self.package = package if path is not None: path = path[:] self.path = path SchemaError.__init__(self, msg, url, lineno, colno) def __str__(self): s = SchemaError.__str__(self) if self.package is not None: s += "\n Package name: " + repr(self.package) if self.filename is not None: s += "\n File name: " + repr(self.filename) if self.package is not None: s += "\n Package path: " + repr(self.path) return s class ConfigurationSyntaxError(_ParseError): """Raised when there's a syntax error in a configuration file.""" class DataConversionError(ConfigurationError, ValueError): """Raised when a data type conversion function raises ValueError.""" def __init__(self, exception, value, position): ConfigurationError.__init__(self, str(exception)) self.exception = exception self.value = value self.lineno, self.colno, self.url = position def __str__(self): s = "%s (line %s" % (self.message, self.lineno) if self.colno is not None: s += ", %s" % self.colno if self.url: s += ", in %s)" % self.url else: s += ")" return s class SubstitutionSyntaxError(ConfigurationError): """Raised when interpolation source text contains syntactical errors.""" class SubstitutionReplacementError(ConfigurationSyntaxError, LookupError): """Raised when no replacement is available for a reference.""" def __init__(self, source, name, url=None, lineno=None): self.source = source self.name = name ConfigurationSyntaxError.__init__( self, "no replacement for " + repr(name), url, lineno) def configureLoggers(text): """Configure one or more loggers from configuration text.""" schema = loadSchemaFile(StringIO.StringIO(""" """)) for factory in loadConfigFile(schema, StringIO.StringIO(text))[0].loggers: factory() ZConfig-3.1.0/ZConfig/cfgparser.py0000644000076500000240000001520412610530667017065 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Configuration parser.""" import ZConfig import ZConfig.url from ZConfig.substitution import isname, substitute class ZConfigParser: __metaclass__ = type __slots__ = ('resource', 'context', 'lineno', 'stack', 'defines', 'file', 'url') def __init__(self, resource, context, defines=None): self.resource = resource self.context = context self.file = resource.file self.url = resource.url self.lineno = 0 self.stack = [] # [(type, name, prevmatcher), ...] if defines is None: defines = {} self.defines = defines def nextline(self): line = self.file.readline() if line: self.lineno += 1 return False, line.strip() else: return True, None def parse(self, section): done, line = self.nextline() while not done: if line[:1] in ("", "#"): # blank line or comment pass elif line[:2] == "": self.error("malformed section end") section = self.end_section(section, line[2:-1]) elif line[0] == "<": # section start if line[-1] != ">": self.error("malformed section start") section = self.start_section(section, line[1:-1]) elif line[0] == "%": self.handle_directive(section, line[1:]) else: self.handle_key_value(section, line) done, line = self.nextline() if self.stack: self.error("unclosed sections not allowed") def start_section(self, section, rest): isempty = rest[-1:] == "/" if isempty: rest = rest[:-1] text = rest.rstrip() # parse section start stuff here m = _section_start_rx.match(text) if not m: self.error("malformed section header") type, name = m.group('type', 'name') type = self._normalize_case(type) if name: name = self._normalize_case(name) try: newsect = self.context.startSection(section, type, name) except ZConfig.ConfigurationError as e: self.error(e.message) if isempty: self.context.endSection(section, type, name, newsect) return section else: self.stack.append((type, name, section)) return newsect def end_section(self, section, rest): if not self.stack: self.error("unexpected section end") type = self._normalize_case(rest.rstrip()) opentype, name, prevsection = self.stack.pop() if type != opentype: self.error("unbalanced section end") try: self.context.endSection( prevsection, type, name, section) except ZConfig.ConfigurationError as e: self.error(e.args[0]) return prevsection def handle_key_value(self, section, rest): m = _keyvalue_rx.match(rest) if not m: self.error("malformed configuration data") key, value = m.group('key', 'value') if not value: value = '' else: value = self.replace(value) try: section.addValue(key, value, (self.lineno, None, self.url)) except ZConfig.ConfigurationError as e: self.error(e.args[0]) def handle_directive(self, section, rest): m = _keyvalue_rx.match(rest) if not m: self.error("missing or unrecognized directive") name, arg = m.group('key', 'value') if name not in ("define", "import", "include"): self.error("unknown directive: " + repr(name)) if not arg: self.error("missing argument to %%%s directive" % name) if name == "include": self.handle_include(section, arg) elif name == "define": self.handle_define(section, arg) elif name == "import": self.handle_import(section, arg) else: assert 0, "unexpected directive for " + repr("%" + rest) def handle_import(self, section, rest): pkgname = self.replace(rest.strip()) self.context.importSchemaComponent(pkgname) def handle_include(self, section, rest): rest = self.replace(rest.strip()) newurl = ZConfig.url.urljoin(self.url, rest) self.context.includeConfiguration(section, newurl, self.defines) def handle_define(self, section, rest): parts = rest.split(None, 1) defname = self._normalize_case(parts[0]) defvalue = '' if len(parts) == 2: defvalue = parts[1] if defname in self.defines: if self.defines[defname] != defvalue: self.error("cannot redefine " + repr(defname)) if not isname(defname): self.error("not a substitution legal name: " + repr(defname)) self.defines[defname] = self.replace(defvalue) def replace(self, text): try: return substitute(text, self.defines) except ZConfig.SubstitutionReplacementError as e: e.lineno = self.lineno e.url = self.url raise def error(self, message): raise ZConfig.ConfigurationSyntaxError(message, self.url, self.lineno) def _normalize_case(self, string): # This method is factored out solely to allow subclasses to modify # the behavior of the parser. return string.lower() import re # _name_re does not allow "(" or ")" for historical reasons. Though # the restriction could be lifted, there seems no need to do so. _name_re = r"[^\s()]+" _keyvalue_rx = re.compile(r"(?P%s)\s*(?P[^\s].*)?$" % _name_re) _section_start_rx = re.compile(r"(?P%s)" r"(?:\s+(?P%s))?" r"$" % (_name_re, _name_re)) del re ZConfig-3.1.0/ZConfig/cmdline.py0000644000076500000240000001365412610530667016533 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for command-line provision of settings. This module provides an extension of the ConfigLoader class which adds a way to add configuration settings from an alternate source. Each setting is described by a string of the form:: some/path/to/key=value """ import ZConfig import ZConfig.loader import ZConfig.matcher class ExtendedConfigLoader(ZConfig.loader.ConfigLoader): def __init__(self, schema): ZConfig.loader.ConfigLoader.__init__(self, schema) self.clopts = [] # [(optpath, value, source-position), ...] def addOption(self, spec, pos=None): if pos is None: pos = "", -1, -1 if "=" not in spec: e = ZConfig.ConfigurationSyntaxError( "invalid configuration specifier", *pos) e.specifier = spec raise e # For now, just add it to the list; not clear that checking # against the schema at this point buys anything. opt, val = spec.split("=", 1) optpath = opt.split("/") if "" in optpath: # // is not allowed in option path e = ZConfig.ConfigurationSyntaxError( "'//' is not allowed in an option path", *pos) e.specifier = spec raise e self.clopts.append((optpath, val, pos)) def createSchemaMatcher(self): if self.clopts: sm = ExtendedSchemaMatcher(self.schema) sm.set_optionbag(self.cook()) else: sm = ZConfig.loader.ConfigLoader.createSchemaMatcher(self) return sm def cook(self): if self.clopts: return OptionBag(self.schema, self.schema, self.clopts) else: return None class OptionBag: def __init__(self, schema, sectiontype, options): self.sectiontype = sectiontype self.schema = schema self.keypairs = {} self.sectitems = [] self._basic_key = schema.registry.get("basic-key") for item in options: optpath, val, pos = item name = sectiontype.keytype(optpath[0]) if len(optpath) == 1: self.add_value(name, val, pos) else: self.sectitems.append(item) def basic_key(self, s, pos): try: return self._basic_key(s) except ValueError: raise ZConfig.ConfigurationSyntaxError( "could not convert basic-key value", *pos) def add_value(self, name, val, pos): if name in self.keypairs: L = self.keypairs[name] else: L = [] self.keypairs[name] = L L.append((val, pos)) def __contains__(self, name): return name in self.keypairs def get_key(self, name): """Return a list of (value, pos) items for the key 'name'. The returned list may be empty. """ L = self.keypairs.get(name) if L: del self.keypairs[name] return L else: return [] def keys(self): return self.keypairs.keys() def get_section_info(self, type, name): L = [] # what pertains to the child section R = [] # what we keep for item in self.sectitems: optpath, val, pos = item s = optpath[0] bk = self.basic_key(s, pos) if name and self._normalize_case(s) == name: L.append((optpath[1:], val, pos)) elif bk == type: L.append((optpath[1:], val, pos)) else: R.append(item) if L: self.sectitems[:] = R return OptionBag(self.schema, self.schema.gettype(type), L) else: return None def finish(self): if self.sectitems or self.keypairs: raise ZConfig.ConfigurationError( "not all command line options were consumed") def _normalize_case(self, string): return string.lower() class MatcherMixin: def set_optionbag(self, bag): self.optionbag = bag def addValue(self, key, value, position): try: realkey = self.type.keytype(key) except ValueError as e: raise ZConfig.DataConversionError(e, key, position) if realkey in self.optionbag: return ZConfig.matcher.BaseMatcher.addValue(self, key, value, position) def createChildMatcher(self, type, name): sm = ZConfig.matcher.BaseMatcher.createChildMatcher(self, type, name) bag = self.optionbag.get_section_info(type.name, name) if bag is not None: sm = ExtendedSectionMatcher( sm.info, sm.type, sm.name, sm.handlers) sm.set_optionbag(bag) return sm def finish_optionbag(self): for key in list(self.optionbag.keys()): for val, pos in self.optionbag.get_key(key): ZConfig.matcher.BaseMatcher.addValue(self, key, val, pos) self.optionbag.finish() class ExtendedSectionMatcher(MatcherMixin, ZConfig.matcher.SectionMatcher): def finish(self): self.finish_optionbag() return ZConfig.matcher.SectionMatcher.finish(self) class ExtendedSchemaMatcher(MatcherMixin, ZConfig.matcher.SchemaMatcher): def finish(self): self.finish_optionbag() return ZConfig.matcher.SchemaMatcher.finish(self) ZConfig-3.1.0/ZConfig/components/0000755000076500000240000000000012610530670016714 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/components/__init__.py0000644000076500000240000000003412610530667021030 0ustar do3ccstaff00000000000000# This is a Python package. ZConfig-3.1.0/ZConfig/components/basic/0000755000076500000240000000000012610530670017775 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/components/basic/__init__.py0000644000076500000240000000003412610530667022111 0ustar do3ccstaff00000000000000# This is a Python package. ZConfig-3.1.0/ZConfig/components/basic/component.xml0000644000076500000240000000031612610530667022527 0ustar do3ccstaff00000000000000 Convenient loader which causes all the "basic" components to be loaded. ZConfig-3.1.0/ZConfig/components/basic/mapping.py0000644000076500000240000000137412610530667022015 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Python datatype for the ZConfig.components.basic.mapping section type.""" def mapping(section): return section.mapping ZConfig-3.1.0/ZConfig/components/basic/mapping.xml0000644000076500000240000000166112610530667022164 0ustar do3ccstaff00000000000000 Section that provides a simple mapping implementation. An application should derive a more specific section type for use in configuration files: <import package="ZConfig.components.basic" file="mapping.xml" /> <sectiontype name="mapping" extends="ZConfig.basic.mapping" /> If a non-standard keytype is needed, it can be overridden as well: <sectiontype name="system-map" extends="ZConfig.basic.mapping" keytype="mypkg.datatypes.system_name" /> ZConfig-3.1.0/ZConfig/components/basic/tests/0000755000076500000240000000000012610530670021137 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/components/basic/tests/__init__.py0000644000076500000240000000003412610530667023253 0ustar do3ccstaff00000000000000# This is a Python package. ZConfig-3.1.0/ZConfig/components/basic/tests/test_mapping.py0000644000076500000240000000501312610530667024210 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the 'basic' section types provided as part of ZConfig.components.basic.""" import ZConfig.tests.support import unittest SIMPLE_SCHEMA = '''\
''' class BasicSectionTypeTestCase( ZConfig.tests.support.TestHelper, unittest.TestCase): schema = None def setUp(self): if self.schema is None: self.__class__.schema = self.load_schema_text(SIMPLE_SCHEMA) def test_simple_empty_dict(self): conf = self.load_config_text(self.schema, "") self.assertEqual(conf.simple_dict, {}) conf = self.load_config_text(self.schema, """\ # comment """) self.assertEqual(conf.simple_dict, {}) def test_simple_dict(self): conf = self.load_config_text(self.schema, """\ key-one value-one key-two value-two """) L = sorted(conf.simple_dict.items()) self.assertEqual(L, [("key-one", "value-one"), ("key-two", "value-two")]) def test_derived_dict(self): conf = self.load_config_text(self.schema, """\ 1 foo 2 bar 42 question? """) L = sorted(conf.int_dict.items()) self.assertEqual(L, [(1, "foo"), (2, "bar"), (42, "question?")]) def test_suite(): return unittest.makeSuite(BasicSectionTypeTestCase) ZConfig-3.1.0/ZConfig/components/logger/0000755000076500000240000000000012610530670020173 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/components/logger/__init__.py0000644000076500000240000000161612610530667022316 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig schema component package for logging configuration.""" # Make sure we can't import this if "logging" isn't available; we # don't want partial imports to appear to succeed. try: import logging except ImportError: import sys del sys.modules[__name__] ZConfig-3.1.0/ZConfig/components/logger/abstract.xml0000644000076500000240000000022712610530667022527 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/components/logger/base-logger.xml0000644000076500000240000000301712610530667023113 0ustar do3ccstaff00000000000000 Base definition for the logger types defined by ZConfig.components.logger. This exists entirely to provide shared key definitions and documentation. Verbosity setting for the logger. Values must be a name of a level, or an integer in the range [0..50]. The names of the levels, in order of increasing verbosity (names on the same line are equivalent): critical, fatal error warn, warning info blather debug trace all The special name "notset", or the numeric value 0, indicates that the setting for the parent logger should be used. It is strongly recommended that names be used rather than numeric values to ensure that configuration files can be deciphered more easily. Handlers to install on this logger. Each handler describes how logging events should be presented. ZConfig-3.1.0/ZConfig/components/logger/component.xml0000644000076500000240000000056712610530667022735 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/components/logger/datatypes.py0000644000076500000240000000215512610530667022554 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig datatypes for logging support.""" _logging_levels = { "critical": 50, "fatal": 50, "error": 40, "warn": 30, "warning": 30, "info": 20, "blather": 15, "debug": 10, "trace": 5, "all": 1, "notset": 0, } def logging_level(value): s = str(value).lower() if s in _logging_levels: return _logging_levels[s] else: v = int(s) if v < 0 or v > 50: raise ValueError("log level not in range: " + repr(v)) return v ZConfig-3.1.0/ZConfig/components/logger/eventlog.xml0000644000076500000240000000073612610530667022554 0ustar do3ccstaff00000000000000 Configuration for the root logger. ZConfig-3.1.0/ZConfig/components/logger/factory.py0000644000076500000240000000244012610530667022222 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## _marker = object() class Factory: """Generic wrapper for instance construction. Calling the factory causes the instance to be created if it hasn't already been created, and returns the object. Calling the factory multiple times returns the same object. The instance is created using the factory's create() method, which must be overriden by subclasses. """ def __init__(self): self.instance = _marker def __call__(self): if self.instance is _marker: self.instance = self.create() return self.instance def create(self): raise NotImplementedError("subclasses need to override create()") ZConfig-3.1.0/ZConfig/components/logger/handlers.py0000644000076500000240000001723612610530667022364 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig factory datatypes for log handlers.""" import sys from ZConfig.components.logger.factory import Factory try: import urlparse except ImportError: # Python 3 support. import urllib.parse as urlparse _log_format_variables = { 'name': '', 'levelno': '3', 'levelname': 'DEBUG', 'pathname': 'apath', 'filename': 'afile', 'module': 'amodule', 'lineno': 1, 'created': 1.1, 'asctime': 'atime', 'msecs': 1, 'relativeCreated': 1, 'thread': 1, 'message': 'amessage', 'process': 1, } def log_format(value): value = ctrl_char_insert(value) try: # Make sure the format string uses only names that will be # provided, and has reasonable type flags for each, and does # not expect positional args. value % _log_format_variables except (ValueError, KeyError): raise ValueError('Invalid log format string %s' % value) return value _control_char_rewrites = {r'\n': '\n', r'\t': '\t', r'\b': '\b', r'\f': '\f', r'\r': '\r'}.items() def ctrl_char_insert(value): for pattern, replacement in _control_char_rewrites: value = value.replace(pattern, replacement) return value def resolve(name): """Given a dotted name, returns an object imported from a Python module.""" name = name.split('.') used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) return found class HandlerFactory(Factory): def __init__(self, section): Factory.__init__(self) self.section = section def create_loghandler(self): raise NotImplementedError( "subclasses must override create_loghandler()") def create(self): import logging logger = self.create_loghandler() if self.section.formatter: f = resolve(self.section.formatter) else: f = logging.Formatter logger.setFormatter(f(self.section.format, self.section.dateformat)) logger.setLevel(self.section.level) return logger def getLevel(self): return self.section.level class FileHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler path = self.section.path max_bytes = self.section.max_size old_files = self.section.old_files when = self.section.when interval = self.section.interval if path == "STDERR": if max_bytes or old_files: raise ValueError("cannot rotate STDERR") handler = loghandler.StreamHandler(sys.stderr) elif path == "STDOUT": if max_bytes or old_files: raise ValueError("cannot rotate STDOUT") handler = loghandler.StreamHandler(sys.stdout) elif when or max_bytes or old_files or interval: if not old_files: raise ValueError("old-files must be set for log rotation") if when: if max_bytes: raise ValueError("can't set *both* max_bytes and when") if not interval: interval = 1 handler = loghandler.TimedRotatingFileHandler( path, when=when, interval=interval, backupCount=old_files) elif max_bytes: handler = loghandler.RotatingFileHandler( path, maxBytes=max_bytes, backupCount=old_files) else: raise ValueError( "max-bytes or when must be set for log rotation") else: handler = loghandler.FileHandler(path) return handler _syslog_facilities = { "auth": 1, "authpriv": 1, "cron": 1, "daemon": 1, "kern": 1, "lpr": 1, "mail": 1, "news": 1, "security": 1, "syslog": 1, "user": 1, "uucp": 1, "local0": 1, "local1": 1, "local2": 1, "local3": 1, "local4": 1, "local5": 1, "local6": 1, "local7": 1, } def syslog_facility(value): value = value.lower() if value not in _syslog_facilities: L = sorted(_syslog_facilities.keys()) raise ValueError("Syslog facility must be one of " + ", ".join(L)) return value class SyslogHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.SysLogHandler(self.section.address.address, self.section.facility) class Win32EventLogFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.Win32EventLogHandler(self.section.appname) def http_handler_url(value): scheme, netloc, path, param, query, fragment = urlparse.urlparse(value) if scheme != 'http': raise ValueError('url must be an http url') if not netloc: raise ValueError('url must specify a location') if not path: raise ValueError('url must specify a path') q = [] if param: q.append(';') q.append(param) if query: q.append('?') q.append(query) if fragment: q.append('#') q.append(fragment) return (netloc, path + ''.join(q)) def get_or_post(value): value = value.upper() if value not in ('GET', 'POST'): raise ValueError('method must be "GET" or "POST", instead received: ' + repr(value)) return value class HTTPHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler host, selector = self.section.url return loghandler.HTTPHandler(host, selector, self.section.method) class SMTPHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler host, port = self.section.smtp_server if not port: mailhost = host else: mailhost = host, port kwargs = {} if self.section.smtp_username and self.section.smtp_password: # Since credentials were only added in py2.6 we use a kwarg to not # break compatibility with older py if sys.version_info < (2, 6): raise ValueError('SMTP auth requires at least Python 2.6.') kwargs['credentials'] = (self.section.smtp_username, self.section.smtp_password) elif (self.section.smtp_username or self.section.smtp_password): raise ValueError( 'Either both smtp-username and smtp-password or none must be ' 'given') return loghandler.SMTPHandler(mailhost, self.section.fromaddr, self.section.toaddrs, self.section.subject, **kwargs) ZConfig-3.1.0/ZConfig/components/logger/handlers.xml0000644000076500000240000000710612610530667022527 0ustar do3ccstaff00000000000000 Base type for most log handlers. This is cannot be used as a loghandler directly since it doesn't implement the loghandler abstract section type. Logging formatter class. The default is 'logging.Formatter'. An alternative is 'zope.exceptions.log.Formatter', which enhances exception tracebacks with information from __traceback_info__ and __traceback_supplement__ variables. ZConfig-3.1.0/ZConfig/components/logger/logger.py0000644000076500000240000000666612610530667022050 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig factory datatypes for loggers.""" from ZConfig.components.logger.factory import Factory class LoggerFactoryBase(Factory): """Base class for logger factories. Factory used to create loggers while delaying actual logger instance construction. We need to do this because we may want to reference a logger before actually instantiating it (for example, to allow the app time to set an effective user). An instance of this wrapper is a callable which, when called, returns a logger object. """ def __init__(self, section): Factory.__init__(self) self.level = section.level self.handler_factories = section.handlers def create(self): # set the logger up import logging logger = logging.getLogger(self.name) logger.setLevel(self.level) if self.handler_factories: for handler_factory in self.handler_factories: handler = handler_factory() logger.addHandler(handler) else: from ZConfig.components.logger import loghandler logger.addHandler(loghandler.NullHandler()) return logger def startup(self): # make sure we've instantiated the logger self() def getLowestHandlerLevel(self): """Return the lowest log level provided by any configured handler. If all handlers and the logger itself have level==NOTSET, this returns NOTSET. """ import logging lowest = self.level for factory in self.handler_factories: level = factory.getLevel() if level != logging.NOTSET: if lowest == logging.NOTSET: lowest = level else: lowest = min(lowest, level) return lowest def reopen(self): """Re-open any handlers for which this is a meaningful operation. This only works on handlers on the logger provided by this factory directly; handlers for child loggers are not affected. (This can be considered a bug, but is sufficient at the moment.) """ logger = self() for handler in logger.handlers: reopen = getattr(handler, "reopen", None) if reopen is not None and callable(reopen): reopen() class EventLogFactory(LoggerFactoryBase): """Logger factory that returns the root logger.""" name = None class LoggerFactory(LoggerFactoryBase): """Logger factory that returns the named logger.""" def __init__(self, section): LoggerFactoryBase.__init__(self, section) self.name = section.name self.propagate = section.propagate def create(self): logger = LoggerFactoryBase.create(self) logger.propagate = self.propagate return logger ZConfig-3.1.0/ZConfig/components/logger/logger.xml0000644000076500000240000000266412610530667022212 0ustar do3ccstaff00000000000000 Indicates whether events that reach this logger should be propogated toward the root of the logger hierarchy. If true (the default), events will be passed to the logger's parent after being handled. If false, events will be handled and the parent will not be informed. There is not a way to control propogation by the severity of the event. The dotted name of the logger. This give it a location in the logging hierarchy. Most applications provide a specific set of subsystem names for which logging is meaning; consult the application documentation for the set of names that are actually interesting for the application. ZConfig-3.1.0/ZConfig/components/logger/loghandler.py0000644000076500000240000001200312610530667022666 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2001 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Handlers which can plug into a PEP 282 logger.""" import os import sys import weakref from logging import Handler, StreamHandler from logging.handlers import RotatingFileHandler as _RotatingFileHandler from logging.handlers import TimedRotatingFileHandler \ as _TimedRotatingFileHandler from logging.handlers import SysLogHandler, BufferingHandler from logging.handlers import HTTPHandler, SMTPHandler from logging.handlers import NTEventLogHandler as Win32EventLogHandler _reopenable_handlers = [] def closeFiles(): """Reopen all logfiles managed by ZConfig configuration.""" while _reopenable_handlers: wr = _reopenable_handlers.pop() h = wr() if h is not None: h.close() def reopenFiles(): """Reopen all logfiles managed by ZConfig configuration.""" for wr in _reopenable_handlers[:]: h = wr() if h is None: try: _reopenable_handlers.remove(wr) except ValueError: continue else: h.reopen() def _remove_from_reopenable(wr): try: _reopenable_handlers.remove(wr) except ValueError: pass class FileHandler(StreamHandler): """File handler which supports reopening of logs. Re-opening should be used instead of the 'rollover' feature of the FileHandler from the standard library's logging package. """ def __init__(self, filename, mode="a"): filename = os.path.abspath(filename) StreamHandler.__init__(self, open(filename, mode)) self.baseFilename = filename self.mode = mode self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): self.stream.close() # This can raise a KeyError if the handler has already been # removed, but a later error can be raised if # StreamHandler.close() isn't called. This seems the best # compromise. :-( try: StreamHandler.close(self) except KeyError: pass _remove_from_reopenable(self._wr) def reopen(self): self.acquire() try: self.stream.close() self.stream = open(self.baseFilename, self.mode) finally: self.release() class Win32FileHandler(FileHandler): """File-based log handler for Windows that supports an additional 'rotate' method. reopen() is generally useless since Windows cannot do a move on an open file. """ def rotate(self, rotateFilename=None): if not rotateFilename: rotateFilename = self.baseFilename + ".last" error = None self.close() try: os.rename(self.baseFilename, rotateFilename) except OSError: pass self.stream = open(self.baseFilename, self.mode) if os.name == "nt": # Make it the default for Windows - we install a 'reopen' handler that # tries to rotate the logfile. FileHandler = Win32FileHandler class RotatingFileHandler(_RotatingFileHandler): def __init__(self, *args, **kw): _RotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): _RotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() class TimedRotatingFileHandler(_TimedRotatingFileHandler): def __init__(self, *args, **kw): _TimedRotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): _TimedRotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() class NullHandler(Handler): """Handler that does nothing.""" def emit(self, record): pass def handle(self, record): pass class StartupHandler(BufferingHandler): """Handler which stores messages in a buffer until later. This is useful at startup before we can know that we can safely write to a configuration-specified handler. """ def __init__(self): BufferingHandler.__init__(self, sys.maxint) def shouldFlush(self, record): return False def flushBufferTo(self, target): while self.buffer: target.handle(self.buffer.pop(0)) ZConfig-3.1.0/ZConfig/components/logger/tests/0000755000076500000240000000000012610530670021335 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/components/logger/tests/__init__.py0000644000076500000240000000003412610530667023451 0ustar do3ccstaff00000000000000# This is a Python package. ZConfig-3.1.0/ZConfig/components/logger/tests/test_logger.py0000644000076500000240000006546212610530667024250 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for logging configuration via ZConfig.""" import doctest import logging import os import sys import tempfile import unittest import ZConfig from ZConfig.components.logger import datatypes from ZConfig.components.logger import handlers from ZConfig.components.logger import loghandler try: import StringIO as StringIO except ImportError: # Python 3 support. import io as StringIO class CustomFormatter(logging.Formatter): def formatException(self, ei): """Format and return the exception information as a string. This adds helpful advice to the end of the traceback. """ import traceback sio = StringIO.StringIO() traceback.print_exception(ei[0], ei[1], ei[2], file=sio) return sio.getvalue() + "... Don't panic!" def read_file(filename): with open(filename) as f: return f.read() class LoggingTestHelper: # Not derived from unittest.TestCase; some test runners seem to # think that means this class contains tests. # XXX This tries to save and restore the state of logging around # the test. Somewhat surgical; there may be a better way. def setUp(self): self._created = [] self._old_logger = logging.getLogger() self._old_level = self._old_logger.level self._old_handlers = self._old_logger.handlers[:] self._old_logger.handlers[:] = [] self._old_logger.setLevel(logging.WARN) self._old_logger_dict = logging.root.manager.loggerDict.copy() logging.root.manager.loggerDict.clear() def tearDown(self): logging.root.manager.loggerDict.clear() logging.root.manager.loggerDict.update(self._old_logger_dict) for h in self._old_logger.handlers: self._old_logger.removeHandler(h) for h in self._old_handlers: self._old_logger.addHandler(h) self._old_logger.setLevel(self._old_level) while self._created: os.unlink(self._created.pop()) self.assertEqual(loghandler._reopenable_handlers, []) loghandler.closeFiles() loghandler._reopenable_handlers == [] def mktemp(self): fd, fn = tempfile.mkstemp() os.close(fd) self._created.append(fn) return fn def move(self, fn): nfn = self.mktemp() os.rename(fn, nfn) return nfn _schema = None def get_schema(self): if self._schema is None: sio = StringIO.StringIO(self._schematext) self.__class__._schema = ZConfig.loadSchemaFile(sio) return self._schema def get_config(self, text): conf, handler = ZConfig.loadConfigFile(self.get_schema(), StringIO.StringIO(text)) self.assertTrue(not handler) return conf class TestConfig(LoggingTestHelper, unittest.TestCase): _schematext = """
""" def test_logging_level(self): # Make sure the expected names are supported; it's not clear # how to check the values in a meaningful way. # Just make sure they're case-insensitive. convert = datatypes.logging_level for name in ["notset", "all", "trace", "debug", "blather", "info", "warn", "warning", "error", "fatal", "critical"]: self.assertEqual(convert(name), convert(name.upper())) self.assertRaises(ValueError, convert, "hopefully-not-a-valid-value") self.assertEqual(convert('10'), 10) self.assertRaises(ValueError, convert, '100') def test_http_method(self): convert = handlers.get_or_post self.assertEqual(convert("get"), "GET") self.assertEqual(convert("GET"), "GET") self.assertEqual(convert("post"), "POST") self.assertEqual(convert("POST"), "POST") self.assertRaises(ValueError, convert, "") self.assertRaises(ValueError, convert, "foo") def test_syslog_facility(self): convert = handlers.syslog_facility for name in ["auth", "authpriv", "cron", "daemon", "kern", "lpr", "mail", "news", "security", "syslog", "user", "uucp", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"]: self.assertEqual(convert(name), name) self.assertEqual(convert(name.upper()), name) self.assertRaises(ValueError, convert, "hopefully-never-a-valid-value") def test_config_without_logger(self): conf = self.get_config("") self.assertTrue(conf.eventlog is None) def test_config_without_handlers(self): logger = self.check_simple_logger("") # Make sure there's a NullHandler, since a warning gets # printed if there are no handlers: self.assertEqual(len(logger.handlers), 1) self.assertTrue(isinstance(logger.handlers[0], loghandler.NullHandler)) def test_with_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertTrue(isinstance(logfile, loghandler.FileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_stderr(self): self.check_standard_stream("stderr") def test_with_stdout(self): self.check_standard_stream("stdout") def test_with_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " max-size 5mb\n" " old-files 10\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 10) self.assertEqual(logfile.maxBytes, 5*1024*1024) self.assertTrue(isinstance(logfile, loghandler.RotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " when D\n" " old-files 11\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 11) self.assertEqual(logfile.interval, 86400) self.assertTrue(isinstance(logfile, loghandler.TimedRotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " when D\n" " interval 3\n" " old-files 11\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 11) self.assertEqual(logfile.interval, 86400*3) self.assertTrue(isinstance(logfile, loghandler.TimedRotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile_and_size_should_fail(self): fn = self.mktemp() self.assertRaises( ValueError, self.check_simple_logger, "\n" " \n" " path %s\n" " level debug\n" " max-size 5mb\n" " when D\n" " old-files 10\n" " \n" "" % fn) def check_standard_stream(self, name): old_stream = getattr(sys, name) conf = self.get_config(""" level info path %s """ % name.upper()) self.assertTrue(conf.eventlog is not None) # The factory has already been created; make sure it picks up # the stderr we set here when we create the logger and # handlers: sio = StringIO.StringIO() setattr(sys, name, sio) try: logger = conf.eventlog() finally: setattr(sys, name, old_stream) logger.warning("woohoo!") self.assertTrue(sio.getvalue().find("woohoo!") >= 0) def test_custom_formatter(self): old_stream = sys.stdout conf = self.get_config(""" formatter ZConfig.components.logger.tests.test_logger.CustomFormatter level info path STDOUT """) sio = StringIO.StringIO() sys.stdout = sio try: logger = conf.eventlog() finally: sys.stdout = old_stream try: raise KeyError except KeyError: logger.exception("testing a KeyError") self.assertTrue(sio.getvalue().find("KeyError") >= 0) self.assertTrue(sio.getvalue().find("Don't panic") >= 0) def test_with_syslog(self): import socket logger = self.check_simple_logger("\n" " \n" " level error\n" " facility local3\n" " \n" "") syslog = logger.handlers[0] self.assertEqual(syslog.level, logging.ERROR) self.assertTrue(isinstance(syslog, loghandler.SysLogHandler)) syslog.close() # avoid ResourceWarning try: syslog.socket.close() # ResourceWarning under 3.2 except socket.SocketError: pass def test_with_http_logger_localhost(self): logger = self.check_simple_logger("\n" " \n" " level error\n" " method post\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.host, "localhost") # XXX The "url" attribute of the handler is misnamed; it # really means just the selector portion of the URL. self.assertEqual(handler.url, "/") self.assertEqual(handler.level, logging.ERROR) self.assertEqual(handler.method, "POST") self.assertTrue(isinstance(handler, loghandler.HTTPHandler)) def test_with_http_logger_remote_host(self): logger = self.check_simple_logger("\n" " \n" " method get\n" " url http://example.com/log/\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.host, "example.com") # XXX The "url" attribute of the handler is misnamed; it # really means just the selector portion of the URL. self.assertEqual(handler.url, "/log/") self.assertEqual(handler.level, logging.NOTSET) self.assertEqual(handler.method, "GET") self.assertTrue(isinstance(handler, loghandler.HTTPHandler)) def test_with_email_notifier(self): logger = self.check_simple_logger("\n" " \n" " to sysadmin@example.com\n" " to sa-pager@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.toaddrs, ["sysadmin@example.com", "sa-pager@example.com"]) self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.level, logging.FATAL) def test_with_email_notifier_with_credentials(self): try: logger = self.check_simple_logger("\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-username john\n" " smtp-password johnpw\n" " \n" "") except ValueError: if sys.version_info >= (2, 6): # For python 2.6 no ValueError must be raised. raise else: # This path must only be reached with python >=2.6 self.assertTrue(sys.version_info >= (2, 6)) handler = logger.handlers[0] self.assertEqual(handler.toaddrs, ["sysadmin@example.com"]) self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.level, logging.FATAL) self.assertEqual(handler.username, 'john') self.assertEqual(handler.password, 'johnpw') def test_with_email_notifier_with_invalid_credentials(self): self.assertRaises(ValueError, self.check_simple_logger, "\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-username john\n" " \n" "") self.assertRaises(ValueError, self.check_simple_logger, "\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-password john\n" " \n" "") def check_simple_logger(self, text, level=logging.INFO): conf = self.get_config(text) self.assertTrue(conf.eventlog is not None) self.assertEqual(conf.eventlog.level, level) logger = conf.eventlog() self.assertTrue(isinstance(logger, logging.Logger)) self.assertEqual(len(logger.handlers), 1) return logger class TestReopeningRotatingLogfiles(LoggingTestHelper, unittest.TestCase): # These tests should not be run on Windows. handler_factory = loghandler.RotatingFileHandler _schematext = """ """ _sampleconfig_template = """ name foo.bar path %(path0)s level debug max-size 1mb old-files 10 path %(path1)s level info max-size 1mb old-files 3 path %(path1)s level info when D old-files 3 name bar.foo path %(path2)s level info max-size 10mb old-files 10 """ def test_filehandler_reopen(self): def mkrecord(msg): # # Python 2.5.0 added an additional required argument to the # LogRecord constructor, making it incompatible with prior # versions. Python 2.5.1 corrected the bug by making the # additional argument optional. We deal with 2.5.0 by adding # the extra arg in only that case, using the default value # from Python 2.5.1. # args = ["foo.bar", logging.ERROR, __file__, 42, msg, (), ()] if sys.version_info[:3] == (2, 5, 0): args.append(None) return logging.LogRecord(*args) # This goes through the reopening operation *twice* to make # sure that we don't lose our handle on the handler the first # time around. fn = self.mktemp() h = self.handler_factory(fn) h.handle(mkrecord("message 1")) nfn1 = self.move(fn) h.handle(mkrecord("message 2")) h.reopen() h.handle(mkrecord("message 3")) nfn2 = self.move(fn) h.handle(mkrecord("message 4")) h.reopen() h.handle(mkrecord("message 5")) h.close() # Check that the messages are in the right files:: text1 = read_file(nfn1) text2 = read_file(nfn2) text3 = read_file(fn) self.assertTrue("message 1" in text1) self.assertTrue("message 2" in text1) self.assertTrue("message 3" in text2) self.assertTrue("message 4" in text2) self.assertTrue("message 5" in text3) def test_logfile_reopening(self): # # This test only applies to the simple logfile reopening; it # doesn't work the same way as the rotating logfile handler. # paths = self.mktemp(), self.mktemp(), self.mktemp() d = { "path0": paths[0], "path1": paths[1], "path2": paths[2], } text = self._sampleconfig_template % d conf = self.get_config(text) self.assertEqual(len(conf.loggers), 2) # Build the loggers from the configuration, and write to them: conf.loggers[0]().info("message 1") conf.loggers[1]().info("message 2") # # We expect this to re-open the original filenames, so we'll # have six files instead of three. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 3") conf.loggers[1]().info("message 4") # # We expect this to re-open the original filenames, so we'll # have nine files instead of six. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 5") conf.loggers[1]().info("message 6") # # We should now have all nine files: for fn in paths: fn1 = fn + ".1" fn2 = fn + ".2" self.assertTrue(os.path.isfile(fn), "%r must exist" % fn) self.assertTrue(os.path.isfile(fn1), "%r must exist" % fn1) self.assertTrue(os.path.isfile(fn2), "%r must exist" % fn2) # # Clean up: for logger in conf.loggers: logger = logger() for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() class TestReopeningLogfiles(TestReopeningRotatingLogfiles): handler_factory = loghandler.FileHandler _sampleconfig_template = """ name foo.bar path %(path0)s level debug path %(path1)s level info name bar.foo path %(path2)s level info """ def test_logfile_reopening(self): # # This test only applies to the simple logfile reopening; it # doesn't work the same way as the rotating logfile handler. # paths = self.mktemp(), self.mktemp(), self.mktemp() d = { "path0": paths[0], "path1": paths[1], "path2": paths[2], } text = self._sampleconfig_template % d conf = self.get_config(text) self.assertEqual(len(conf.loggers), 2) # Build the loggers from the configuration, and write to them: conf.loggers[0]().info("message 1") conf.loggers[1]().info("message 2") npaths1 = [self.move(fn) for fn in paths] # # We expect this to re-open the original filenames, so we'll # have six files instead of three. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 3") conf.loggers[1]().info("message 4") npaths2 = [self.move(fn) for fn in paths] # # We expect this to re-open the original filenames, so we'll # have nine files instead of six. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 5") conf.loggers[1]().info("message 6") # # We should now have all nine files: for fn in paths: self.assertTrue(os.path.isfile(fn), "%r must exist" % fn) for fn in npaths1: self.assertTrue(os.path.isfile(fn), "%r must exist" % fn) for fn in npaths2: self.assertTrue(os.path.isfile(fn), "%r must exist" % fn) # # Clean up: for logger in conf.loggers: logger = logger() for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_filehandler_reopen_thread_safety(self): # The reopen method needs to do locking to avoid a race condition # with emit calls. For simplicity we replace the "acquire" and # "release" methods with dummies that record calls to them. fn = self.mktemp() h = self.handler_factory(fn) calls = [] h.acquire = lambda: calls.append("acquire") h.release = lambda: calls.append("release") h.reopen() h.close() self.assertEqual(calls, ["acquire", "release"]) def test_logger_convenience_function_and_ommiting_name_to_get_root_logger(): """ The ZConfig.loggers function can be used to configure one or more loggers. We'll configure the rot logger and a non-root logger. >>> old_level = logging.getLogger().getEffectiveLevel() >>> old_handler_count = len(logging.getLogger().handlers) >>> ZConfig.configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format root %(levelname)s %(name)s %(message)s ... ... ... ... ... name ZConfig.TEST ... level DEBUG ... ... PATH STDOUT ... format test %(levelname)s %(name)s %(message)s ... ... ... ''') >>> logging.getLogger('ZConfig.TEST').debug('test message') test DEBUG ZConfig.TEST test message root DEBUG ZConfig.TEST test message >>> logging.getLogger().getEffectiveLevel() == logging.INFO True >>> len(logging.getLogger().handlers) == old_handler_count + 1 True >>> logging.getLogger('ZConfig.TEST').getEffectiveLevel() == logging.DEBUG True >>> len(logging.getLogger('ZConfig.TEST').handlers) == 1 True .. cleanup >>> logging.getLogger('ZConfig.TEST').setLevel(logging.NOTSET) >>> logging.getLogger('ZConfig.TEST').removeHandler( ... logging.getLogger('ZConfig.TEST').handlers[-1]) >>> logging.getLogger().setLevel(old_level) >>> logging.getLogger().removeHandler(logging.getLogger().handlers[-1]) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(unittest.makeSuite(TestConfig)) if os.name != "nt": # Though log files can be closed and re-opened on Windows, these # tests expect to be able to move the underlying files out from # underneath the logger while open. That's not possible on # Windows. # # Different tests are needed that only test that close/re-open # operations are performed by the handler; those can be run on # any platform. suite.addTest(unittest.makeSuite(TestReopeningLogfiles)) suite.addTest(unittest.makeSuite(TestReopeningRotatingLogfiles)) return suite if __name__ == '__main__': unittest.main(defaultTest="test_suite") ZConfig-3.1.0/ZConfig/datatypes.py0000644000076500000240000003472112610530667017114 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Selection of standard datatypes for ZConfig.""" import os import re import sys import datetime try: unicode except NameError: have_unicode = False else: have_unicode = True class MemoizedConversion: """Conversion helper that caches the results of expensive conversions.""" def __init__(self, conversion): self._memo = {} self._conversion = conversion def __call__(self, value): try: return self._memo[value] except KeyError: v = self._conversion(value) self._memo[value] = v return v class RangeCheckedConversion: """Conversion helper that range checks another conversion.""" def __init__(self, conversion, min=None, max=None): self._min = min self._max = max self._conversion = conversion def __call__(self, value): v = self._conversion(value) if self._min is not None and v < self._min: raise ValueError("%s is below lower bound (%s)" % (repr(v), repr(self._min))) if self._max is not None and v > self._max: raise ValueError("%s is above upper bound (%s)" % (repr(v), repr(self._max))) return v class RegularExpressionConversion: reason = "value did not match regular expression" def __init__(self, regex): self._rx = re.compile(regex) def __call__(self, value): m = self._rx.match(value) if m and m.group() == value: return value else: raise ValueError("%s: %s" % (self.reason, repr(value))) def check_locale(value): import locale prev = locale.setlocale(locale.LC_ALL) try: try: locale.setlocale(locale.LC_ALL, value) finally: locale.setlocale(locale.LC_ALL, prev) except locale.Error: raise ValueError( 'The specified locale "%s" is not supported by your system.\n' 'See your operating system documentation for more\n' 'information on locale support.' % value) else: return value class BasicKeyConversion(RegularExpressionConversion): def __init__(self): RegularExpressionConversion.__init__(self, "[a-zA-Z][-._a-zA-Z0-9]*") def __call__(self, value): value = str(value) return RegularExpressionConversion.__call__(self, value).lower() class ASCIIConversion(RegularExpressionConversion): def __call__(self, value): value = RegularExpressionConversion.__call__(self, value) if have_unicode and isinstance(value, unicode): value = value.encode("ascii") return value _ident_re = "[_a-zA-Z][_a-zA-Z0-9]*" class IdentifierConversion(ASCIIConversion): reason = "not a valid Python identifier" def __init__(self): ASCIIConversion.__init__(self, _ident_re) class DottedNameConversion(ASCIIConversion): reason = "not a valid dotted name" def __init__(self): ASCIIConversion.__init__(self, r"%s(?:\.%s)*" % (_ident_re, _ident_re)) class DottedNameSuffixConversion(ASCIIConversion): reason = "not a valid dotted name or suffix" def __init__(self): ASCIIConversion.__init__(self, r"(?:%s)(?:\.%s)*|(?:\.%s)+" % (_ident_re, _ident_re, _ident_re)) def integer(value): return int(value) def null_conversion(value): return value def asBoolean(s): """Convert a string value to a boolean value.""" ss = str(s).lower() if ss in ('yes', 'true', 'on'): return True elif ss in ('no', 'false', 'off'): return False else: raise ValueError("not a valid boolean value: " + repr(s)) def string_list(s): """Convert a string to a list of strings using .split().""" return s.split() port_number = RangeCheckedConversion(integer, min=0, max=0xffff).__call__ class InetAddress: def __init__(self, default_host): self.DEFAULT_HOST = default_host def __call__(self, s): # returns (host, port) tuple host = '' port = None if ":" in s: host, p = s.rsplit(":", 1) if host.startswith('[') and host.endswith(']'): # [IPv6]:port host = host[1:-1] elif ':' in host: # Unbracketed IPv6 address; # last part is not the port number host = s p = None if p: # else leave port at None port = port_number(p) host = host.lower() else: try: port = port_number(s) except ValueError: if len(s.split()) != 1: raise ValueError("not a valid host name: " + repr(s)) host = s.lower() if not host: host = self.DEFAULT_HOST return host, port if sys.platform[:3] == "win": DEFAULT_HOST = "localhost" else: DEFAULT_HOST = "" inet_address = InetAddress(DEFAULT_HOST) inet_connection_address = InetAddress("127.0.0.1") inet_binding_address = InetAddress("") class SocketAddress: # Parsing results in family and address # Family can be AF_UNIX (for addresses that are path names) # or AF_INET6 (for inet addresses with colons in them) # or AF_INET (for all other inet addresses); # An inet address is a (host, port) pair # Notice that no DNS lookup is performed, so if the host # is a DNS name, DNS lookup may end up with either IPv4 or # IPv6 addresses, or both def __init__(self, s): import socket if "/" in s or s.find(os.sep) >= 0: self.family = getattr(socket, "AF_UNIX", None) self.address = s else: self.family = socket.AF_INET self.address = self._parse_address(s) if ':' in self.address[0]: self.family = socket.AF_INET6 def _parse_address(self, s): return inet_address(s) class SocketBindingAddress(SocketAddress): def _parse_address(self, s): return inet_binding_address(s) class SocketConnectionAddress(SocketAddress): def _parse_address(self, s): return inet_connection_address(s) def float_conversion(v): try: is_str = isinstance(v, basestring) except NameError: # Python 3 support. is_str = isinstance(v, str) if is_str: if v.lower() in ["inf", "-inf", "nan"]: raise ValueError(repr(v) + " is not a portable float representation") return float(v) class IpaddrOrHostname(RegularExpressionConversion): def __init__(self): # IP address regex from the Perl Cookbook, Recipe 6.23 (revised ed.) # We allow underscores in hostnames although this is considered # illegal according to RFC1034. # Addition: IPv6 addresses are now also accepted expr = (r"(^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])$)" #ipaddr cont'd r"|([A-Za-z_][-A-Za-z0-9_.]*[-A-Za-z0-9_])" # or hostname r"|([0-9A-Fa-f:.]+:[0-9A-Fa-f:.]*)" # or superset of IPv6 addresses # (requiring at least one colon) ) RegularExpressionConversion.__init__(self, expr) def __call__(self, value): result = RegularExpressionConversion.__call__(self, value).lower() # Use C library to validate IPv6 addresses, in particular wrt. # number of colons and number of digits per group if ':' in result: import socket try: socket.inet_pton(socket.AF_INET6, result) except socket.error: raise ValueError('%r is not a valid IPv6 address' % value) return result def existing_directory(v): nv = os.path.expanduser(v) if os.path.isdir(nv): return nv raise ValueError('%s is not an existing directory' % v) def existing_path(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing path' % v) def existing_file(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing file' % v) def existing_dirpath(v): nv = os.path.expanduser(v) dir = os.path.dirname(nv) if not dir: # relative pathname with no directory component return nv if os.path.isdir(dir): return nv raise ValueError('The directory named as part of the path %s ' 'does not exist.' % v) class SuffixMultiplier: # d is a dictionary of suffixes to integer multipliers. If no suffixes # match, default is the multiplier. Matches are case insensitive. Return # values are in the fundamental unit. def __init__(self, d, default=1): self._d = d self._default = default # all keys must be the same size self._keysz = None for k in d.keys(): if self._keysz is None: self._keysz = len(k) else: assert self._keysz == len(k) def __call__(self, v): v = v.lower() for s, m in self._d.items(): if v[-self._keysz:] == s: return int(v[:-self._keysz]) * m return int(v) * self._default def timedelta(s): # Unlike the standard time-interval data type, which returns a float # number of seconds, this datatype takes a wider range of syntax and # returns a datetime.timedelta # # Accepts suffixes: # w - weeks # d - days # h - hours # m - minutes # s - seconds # # and all arguments may be integers or floats, positive or negative. # More than one time interval suffix value may appear on the line, but # they should all be separated by spaces, e.g.: # # sleep_time 4w 2d 7h 12m 0.00001s weeks = days = hours = minutes = seconds = 0 for part in s.split(): val = float(part[:-1]) suffix = part[-1] if suffix == 'w': weeks = val elif suffix == 'd': days = val elif suffix == 'h': hours = val elif suffix == 'm': minutes = val elif suffix == 's': seconds = val else: raise TypeError('bad part %s in %s' % (part, s)) return datetime.timedelta(weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) stock_datatypes = { "boolean": asBoolean, "dotted-name": DottedNameConversion(), "dotted-suffix": DottedNameSuffixConversion(), "identifier": IdentifierConversion(), "integer": integer, "float": float_conversion, "string": str, "string-list": string_list, "null": null_conversion, "locale": MemoizedConversion(check_locale), "port-number": port_number, "basic-key": BasicKeyConversion(), "inet-address": inet_address, "inet-binding-address": inet_binding_address, "inet-connection-address": inet_connection_address, "socket-address": SocketAddress, "socket-binding-address": SocketBindingAddress, "socket-connection-address": SocketConnectionAddress, "ipaddr-or-hostname":IpaddrOrHostname(), "existing-directory":existing_directory, "existing-path": existing_path, "existing-file": existing_file, "existing-dirpath": existing_dirpath, "byte-size": SuffixMultiplier({'kb': 1024, 'mb': 1024*1024, 'gb': 1024*1024*1024, }), "time-interval": SuffixMultiplier({'s': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24, }), "timedelta": timedelta, } class Registry: def __init__(self, stock=None): if stock is None: stock = stock_datatypes.copy() self._stock = stock self._other = {} self._basic_key = None def get(self, name): if '.' not in name: if self._basic_key is None: self._basic_key = self._other.get("basic-key") if self._basic_key is None: self._basic_key = self._stock.get("basic-key") if self._basic_key is None: self._basic_key = stock_datatypes["basic-key"] name = self._basic_key(name) t = self._stock.get(name) if t is None: t = self._other.get(name) if t is None: t = self.search(name) return t def register(self, name, conversion): if name in self._stock: raise ValueError("datatype name conflicts with built-in type: " + repr(name)) if name in self._other: raise ValueError("datatype name already registered: " + repr(name)) self._other[name] = conversion def search(self, name): if not "." in name: raise ValueError("unloadable datatype name: " + repr(name)) components = name.split('.') start = components[0] g = {} package = __import__(start, g, g) modulenames = [start] for component in components[1:]: modulenames.append(component) try: package = getattr(package, component) except AttributeError: n = '.'.join(modulenames) package = __import__(n, g, g, component) self._other[name] = package return package ZConfig-3.1.0/ZConfig/info.py0000644000076500000240000004145312610530667016051 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Objects that can describe a ZConfig schema.""" import copy import ZConfig class UnboundedThing: __metaclass__ = type __slots__ = () def __lt__(self, other): return False def __le__(self, other): return isinstance(other, self.__class__) def __gt__(self, other): return True def __ge__(self, other): return True def __eq__(self, other): return isinstance(other, self.__class__) def __ne__(self, other): return not isinstance(other, self.__class__) def __repr__(self): return "" Unbounded = UnboundedThing() class ValueInfo: __metaclass__ = type __slots__ = 'value', 'position' def __init__(self, value, position): self.value = value # position is (lineno, colno, url) self.position = position def convert(self, datatype): try: return datatype(self.value) except ValueError as e: raise ZConfig.DataConversionError(e, self.value, self.position) class BaseInfo: """Information about a single configuration key.""" description = None example = None metadefault = None def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): if maxOccurs is not None and maxOccurs < 1: if maxOccurs < 1: raise ZConfig.SchemaError( "maxOccurs must be at least 1") if minOccurs is not None and minOccurs < maxOccurs: raise ZConfig.SchemaError( "minOccurs must be at least maxOccurs") self.name = name self.datatype = datatype self.minOccurs = minOccurs self.maxOccurs = maxOccurs self.handler = handler self.attribute = attribute def __repr__(self): clsname = self.__class__.__name__ return "<%s for %s>" % (clsname, repr(self.name)) def isabstract(self): return False def ismulti(self): return self.maxOccurs > 1 def issection(self): return False class BaseKeyInfo(BaseInfo): _rawdefaults = None def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): assert minOccurs is not None BaseInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) self._finished = False def finish(self): if self._finished: raise ZConfig.SchemaError( "cannot finish KeyInfo more than once") self._finished = True def adddefault(self, value, position, key=None): if self._finished: raise ZConfig.SchemaError( "cannot add default values to finished KeyInfo") # Check that the name/keyed relationship is right: if self.name == "+" and key is None: raise ZConfig.SchemaError( "default values must be keyed for name='+'") elif self.name != "+" and key is not None: raise ZConfig.SchemaError( "unexpected key for default value") self.add_valueinfo(ValueInfo(value, position), key) def add_valueinfo(self, vi, key): """Actually add a ValueInfo to this key-info object. The appropriate value of None-ness of key has already been checked with regard to the name of the key, and has been found permissible to add. This method is a requirement for subclasses, and should not be called by client code. """ raise NotImplementedError( "add_valueinfo() must be implemented by subclasses of BaseKeyInfo") def prepare_raw_defaults(self): assert self.name == "+" if self._rawdefaults is None: self._rawdefaults = self._default self._default = {} class KeyInfo(BaseKeyInfo): _default = None def __init__(self, name, datatype, minOccurs, handler, attribute): BaseKeyInfo.__init__(self, name, datatype, minOccurs, 1, handler, attribute) if self.name == "+": self._default = {} def add_valueinfo(self, vi, key): if self.name == "+": if key in self._default: # not ideal: we're presenting the unconverted # version of the key raise ZConfig.SchemaError( "duplicate default value for key %s" % repr(key)) self._default[key] = vi elif self._default is not None: raise ZConfig.SchemaError( "cannot set more than one default to key with maxOccurs == 1") else: self._default = vi def computedefault(self, keytype): self.prepare_raw_defaults() for k, vi in self._rawdefaults.items(): key = ValueInfo(k, vi.position).convert(keytype) self.add_valueinfo(vi, key) def getdefault(self): # Use copy.copy() to make sure we don't allow polution of # our internal data without having to worry about both the # list and dictionary cases: return copy.copy(self._default) class MultiKeyInfo(BaseKeyInfo): def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): BaseKeyInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) if self.name == "+": self._default = {} else: self._default = [] def add_valueinfo(self, vi, key): if self.name == "+": # This is a keyed value, not a simple value: if key in self._default: self._default[key].append(vi) else: self._default[key] = [vi] else: self._default.append(vi) def computedefault(self, keytype): self.prepare_raw_defaults() for k, vlist in self._rawdefaults.items(): key = ValueInfo(k, vlist[0].position).convert(keytype) for vi in vlist: self.add_valueinfo(vi, key) def getdefault(self): return copy.copy(self._default) class SectionInfo(BaseInfo): def __init__(self, name, sectiontype, minOccurs, maxOccurs, handler, attribute): # name - name of the section; one of '*', '+', or name1 # sectiontype - SectionType instance # minOccurs - minimum number of occurances of the section # maxOccurs - maximum number of occurances; if > 1, name # must be '*' or '+' # handler - handler name called when value(s) must take effect, # or None # attribute - name of the attribute on the SectionValue object if maxOccurs > 1: if name not in ('*', '+'): raise ZConfig.SchemaError( "sections which can occur more than once must" " use a name of '*' or '+'") if not attribute: raise ZConfig.SchemaError( "sections which can occur more than once must" " specify a target attribute name") if sectiontype.isabstract(): datatype = None else: datatype = sectiontype.datatype BaseInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) self.sectiontype = sectiontype def __repr__(self): clsname = self.__class__.__name__ return "<%s for %s (%s)>" % ( clsname, self.sectiontype.name, repr(self.name)) def issection(self): return True def allowUnnamed(self): return self.name == "*" def isAllowedName(self, name): if name == "*" or name == "+": return False elif self.name == "+": return name and True or False elif self.name == "*": return True else: return name == self.name def getdefault(self): # sections cannot have defaults if self.maxOccurs > 1: return [] else: return None class AbstractType: __metaclass__ = type __slots__ = '_subtypes', 'name', 'description' def __init__(self, name): self._subtypes = {} self.name = name self.description = None def addsubtype(self, type): self._subtypes[type.name] = type def getsubtype(self, name): try: return self._subtypes[name] except KeyError: raise ZConfig.SchemaError("no sectiontype %s in abstracttype %s" % (repr(name), repr(self.name))) def hassubtype(self, name): """Return true iff this type has 'name' as a concrete manifestation.""" return name in self._subtypes.keys() def getsubtypenames(self): """Return the names of all concrete types as a sorted list.""" return sorted(self._subtypes.keys()) def isabstract(self): return True class SectionType: def __init__(self, name, keytype, valuetype, datatype, registry, types): # name - name of the section, or '*' or '+' # datatype - type for the section itself # keytype - type for the keys themselves # valuetype - default type for key values self.name = name self.datatype = datatype self.keytype = keytype self.valuetype = valuetype self.handler = None self.description = None self.registry = registry self._children = [] # [(key, info), ...] self._attrmap = {} # {attribute: info, ...} self._keymap = {} # {key: info, ...} self._types = types def gettype(self, name): n = name.lower() try: return self._types[n] except KeyError: raise ZConfig.SchemaError("unknown type name: " + repr(name)) def gettypenames(self): return list(self._types.keys()) def __len__(self): return len(self._children) def __getitem__(self, index): return self._children[index] def _add_child(self, key, info): # check naming constraints assert key or info.attribute if key and key in self._keymap: raise ZConfig.SchemaError( "child name %s already used" % key) if info.attribute and info.attribute in self._attrmap: raise ZConfig.SchemaError( "child attribute name %s already used" % info.attribute) # a-ok, add the item to the appropriate maps if info.attribute: self._attrmap[info.attribute] = info if key: self._keymap[key] = info self._children.append((key, info)) def addkey(self, keyinfo): self._add_child(keyinfo.name, keyinfo) def addsection(self, name, sectinfo): assert name not in ("*", "+") self._add_child(name, sectinfo) def getinfo(self, key): if not key: raise ZConfig.ConfigurationError( "cannot match a key without a name") try: return self._keymap[key] except KeyError: raise ZConfig.ConfigurationError("no key matching " + repr(key)) def getrequiredtypes(self): d = {} if self.name: d[self.name] = 1 stack = [self] while stack: info = stack.pop() for key, ci in info._children: if ci.issection(): t = ci.sectiontype if t.name not in d: d[t.name] = 1 stack.append(t) return list(d.keys()) def getsectioninfo(self, type, name): for key, info in self._children: if key: if key == name: if not info.issection(): raise ZConfig.ConfigurationError( "section name %s already in use for key" % key) st = info.sectiontype if st.isabstract(): try: st = st.getsubtype(type) except ZConfig.ConfigurationError: raise ZConfig.ConfigurationError( "section type %s not allowed for name %s" % (repr(type), repr(key))) if not st.name == type: raise ZConfig.ConfigurationError( "name %s must be used for a %s section" % (repr(name), repr(st.name))) return info # else must be a sectiontype or an abstracttype: elif info.sectiontype.name == type: if not (name or info.allowUnnamed()): raise ZConfig.ConfigurationError( repr(type) + " sections must be named") return info elif info.sectiontype.isabstract(): st = info.sectiontype if st.name == type: raise ZConfig.ConfigurationError( "cannot define section with an abstract type") try: st = st.getsubtype(type) except ZConfig.ConfigurationError: # not this one; maybe a different one pass else: return info raise ZConfig.ConfigurationError( "no matching section defined for type='%s', name='%s'" % ( type, name)) def isabstract(self): return False class SchemaType(SectionType): def __init__(self, keytype, valuetype, datatype, handler, url, registry): SectionType.__init__(self, None, keytype, valuetype, datatype, registry, {}) self._components = {} self.handler = handler self.url = url def addtype(self, typeinfo): n = typeinfo.name if n in self._types: raise ZConfig.SchemaError("type name cannot be redefined: " + repr(typeinfo.name)) self._types[n] = typeinfo def allowUnnamed(self): return True def isAllowedName(self, name): return False def issection(self): return True def getunusedtypes(self): alltypes = self.gettypenames() reqtypes = self.getrequiredtypes() for n in reqtypes: alltypes.remove(n) if self.name and self.name in alltypes: alltypes.remove(self.name) return alltypes def createSectionType(self, name, keytype, valuetype, datatype): t = SectionType(name, keytype, valuetype, datatype, self.registry, self._types) self.addtype(t) return t def deriveSectionType(self, base, name, keytype, valuetype, datatype): if isinstance(base, SchemaType): raise ZConfig.SchemaError( "cannot derive sectiontype from top-level schema") t = self.createSectionType(name, keytype, valuetype, datatype) t._attrmap.update(base._attrmap) t._keymap.update(base._keymap) t._children.extend(base._children) for i in range(len(t._children)): key, info = t._children[i] if isinstance(info, BaseKeyInfo) and info.name == "+": # need to create a new info object and recompute the # default mapping based on the new keytype info = copy.copy(info) info.computedefault(t.keytype) t._children[i] = (key, info) return t def addComponent(self, name): if name in self._components: raise ZConfig.SchemaError("already have component %s" % name) self._components[name] = name def hasComponent(self, name): return name in self._components def createDerivedSchema(base): new = SchemaType(base.keytype, base.valuetype, base.datatype, base.handler, base.url, base.registry) new._components.update(base._components) new.description = base.description new._children[:] = base._children new._attrmap.update(base._attrmap) new._keymap.update(base._keymap) new._types.update(base._types) return new ZConfig-3.1.0/ZConfig/loader.py0000644000076500000240000002762412610530667016370 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Schema loader utility.""" import os.path import re import sys import urllib import ZConfig import ZConfig.cfgparser import ZConfig.datatypes import ZConfig.info import ZConfig.matcher import ZConfig.schema import ZConfig.url try: import StringIO as StringIO except ImportError: # Python 3 support. import io as StringIO try: import urllib2 except ImportError: # Python 3 support import urllib.request as urllib2 try: from urllib import pathname2url except ImportError: # Python 3 support from urllib.request import pathname2url def loadSchema(url): return SchemaLoader().loadURL(url) def loadSchemaFile(file, url=None): return SchemaLoader().loadFile(file, url) def loadConfig(schema, url, overrides=()): return _get_config_loader(schema, overrides).loadURL(url) def loadConfigFile(schema, file, url=None, overrides=()): return _get_config_loader(schema, overrides).loadFile(file, url) def _get_config_loader(schema, overrides): if overrides: from ZConfig import cmdline loader = cmdline.ExtendedConfigLoader(schema) for opt in overrides: loader.addOption(opt) else: loader = ConfigLoader(schema) return loader class BaseLoader: def __init__(self): pass def createResource(self, file, url): return Resource(file, url) def loadURL(self, url): url = self.normalizeURL(url) r = self.openResource(url) try: return self.loadResource(r) finally: r.close() def loadFile(self, file, url=None): if not url: url = _url_from_file(file) r = self.createResource(file, url) try: return self.loadResource(r) finally: r.close() # utilities def loadResource(self, resource): raise NotImplementedError( "BaseLoader.loadResource() must be overridden by a subclass") def openResource(self, url): # ConfigurationError exceptions raised here should be # str()able to generate a message for an end user. # # XXX This should be replaced to use a local cache for remote # resources. The policy needs to support both re-retrieve on # change and provide the cached resource when the remote # resource is not accessible. url = str(url) if url.startswith("package:"): _, package, filename = url.split(":", 2) file = openPackageResource(package, filename) else: try: file = urllib2.urlopen(url) except urllib2.URLError as e: # urllib2.URLError has a particularly hostile str(), so we # generally don't want to pass it along to the user. self._raise_open_error(url, e.reason) except (IOError, OSError) as e: # Python 2.1 raises a different error from Python 2.2+, # so we catch both to make sure we detect the situation. self._raise_open_error(url, str(e)) if sys.version_info[0] >= 3: # Python 3 support: file.read() returns bytes, so we convert it # to an StringIO. (Can't use io.TextIOWrapper because of # http://bugs.python.org/issue16723 and probably other bugs) try: data = file.read().decode() finally: file.close() file = StringIO.StringIO(data) return self.createResource(file, url) def _raise_open_error(self, url, message): if url[:7].lower() == "file://": what = "file" ident = urllib2.url2pathname(url[7:]) else: what = "URL" ident = url raise ZConfig.ConfigurationError( "error opening %s %s: %s" % (what, ident, message), url) def normalizeURL(self, url): if self.isPath(url): url = "file://" + pathname2url(os.path.abspath(url)) newurl, fragment = ZConfig.url.urldefrag(url) if fragment: raise ZConfig.ConfigurationError( "fragment identifiers are not supported", url) return newurl # from RFC 3986: # schema = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) _pathsep_rx = re.compile(r"[a-zA-Z][-+.a-zA-Z0-9]*:") def isPath(self, s): """Return True iff 's' should be handled as a filesystem path.""" if ":" in s: # XXX This assumes that one-character scheme identifiers # are always Windows drive letters; I don't know of any # one-character scheme identifiers. m = self._pathsep_rx.match(s) if m is None: return True # Does it look like a drive letter? return len(m.group(0)) == 2 else: return True def openPackageResource(package, path): __import__(package) pkg = sys.modules[package] try: loader = pkg.__loader__ except AttributeError: relpath = os.path.join(*path.split("/")) for dir in pkg.__path__: filename = os.path.join(dir, relpath) if os.path.exists(filename): break else: raise ZConfig.SchemaResourceError("schema component not found", filename=path, package=package, path=pkg.__path__) url = "file:" + pathname2url(filename) url = ZConfig.url.urlnormalize(url) return urllib2.urlopen(url) else: for dir in pkg.__path__: loadpath = os.path.join(dir, path) try: return StringIO.StringIO( loader.get_data(loadpath).decode('utf-8')) except Exception: pass raise ZConfig.SchemaResourceError("schema component not found", filename=path, package=package, path=pkg.__path__) def _url_from_file(file): name = getattr(file, "name", None) if name and name[0] != "<" and name[-1] != ">": return "file://" + pathname2url(os.path.abspath(name)) else: return None class SchemaLoader(BaseLoader): def __init__(self, registry=None): if registry is None: registry = ZConfig.datatypes.Registry() BaseLoader.__init__(self) self.registry = registry self._cache = {} def loadResource(self, resource): if resource.url and resource.url in self._cache: schema = self._cache[resource.url] else: schema = ZConfig.schema.parseResource(resource, self) self._cache[resource.url] = schema return schema # schema parser support API def schemaComponentSource(self, package, file): parts = package.split(".") if not parts: raise ZConfig.SchemaError( "illegal schema component name: " + repr(package)) if "" in parts: # '' somewhere in the package spec; still illegal raise ZConfig.SchemaError( "illegal schema component name: " + repr(package)) file = file or "component.xml" try: __import__(package) except ImportError as e: raise ZConfig.SchemaResourceError( "could not load package %s: %s" % (package, str(e)), filename=file, package=package) pkg = sys.modules[package] if not hasattr(pkg, "__path__"): raise ZConfig.SchemaResourceError( "import name does not refer to a package", filename=file, package=package) return "package:%s:%s" % (package, file) class ConfigLoader(BaseLoader): def __init__(self, schema): if schema.isabstract(): raise ZConfig.SchemaError( "cannot check a configuration an abstract type") BaseLoader.__init__(self) self.schema = schema self._private_schema = False def loadResource(self, resource): sm = self.createSchemaMatcher() self._parse_resource(sm, resource) result = sm.finish(), CompositeHandler(sm.handlers, self.schema) return result def createSchemaMatcher(self): return ZConfig.matcher.SchemaMatcher(self.schema) # config parser support API def startSection(self, parent, type, name): t = self.schema.gettype(type) if t.isabstract(): raise ZConfig.ConfigurationError( "concrete sections cannot match abstract section types;" " found abstract type " + repr(type)) return parent.createChildMatcher(t, name) def endSection(self, parent, type, name, matcher): sectvalue = matcher.finish() parent.addSection(type, name, sectvalue) def importSchemaComponent(self, pkgname): schema = self.schema if not self._private_schema: # replace the schema with an extended schema on the first %import self._loader = SchemaLoader(self.schema.registry) schema = ZConfig.info.createDerivedSchema(self.schema) self._private_schema = True self.schema = schema url = self._loader.schemaComponentSource(pkgname, '') if schema.hasComponent(url): return resource = self.openResource(url) schema.addComponent(url) try: ZConfig.schema.parseComponent(resource, self._loader, schema) finally: resource.close() def includeConfiguration(self, section, url, defines): url = self.normalizeURL(url) r = self.openResource(url) try: self._parse_resource(section, r, defines) finally: r.close() # internal helper def _parse_resource(self, matcher, resource, defines=None): parser = ZConfig.cfgparser.ZConfigParser(resource, self, defines) parser.parse(matcher) class CompositeHandler: def __init__(self, handlers, schema): self._handlers = handlers self._convert = schema.registry.get("basic-key") def __call__(self, handlermap): d = {} for name, callback in handlermap.items(): n = self._convert(name) if n in d: raise ZConfig.ConfigurationError( "handler name not unique when converted to a basic-key: " + repr(name)) d[n] = callback L = [] for handler, value in self._handlers: if handler not in d: L.append(handler) if L: raise ZConfig.ConfigurationError( "undefined handlers: " + ", ".join(L)) for handler, value in self._handlers: f = d[handler] if f is not None: f(value) def __len__(self): return len(self._handlers) class Resource: def __init__(self, file, url): self.file = file self.url = url def close(self): if self.file is not None: self.file.close() self.file = None self.closed = True def __getattr__(self, name): return getattr(self.file, name) ZConfig-3.1.0/ZConfig/matcher.py0000644000076500000240000002500712610530667016536 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Utility that manages the binding of configuration data to a section.""" import ZConfig from ZConfig.info import ValueInfo class BaseMatcher: def __init__(self, info, type, handlers): self.info = info self.type = type self._values = {} for key, info in type: if info.name == "+" and not info.issection(): v = {} elif info.ismulti(): v = [] else: v = None assert info.attribute is not None self._values[info.attribute] = v self._sectionnames = {} if handlers is None: handlers = [] self.handlers = handlers def __repr__(self): clsname = self.__class__.__name__ extra = "type " + repr(self.type.name) return "<%s for %s>" % (clsname, extra) def addSection(self, type, name, sectvalue): if name: if name in self._sectionnames: raise ZConfig.ConfigurationError( "section names must not be re-used within the" " same container:" + repr(name)) self._sectionnames[name] = name ci = self.type.getsectioninfo(type, name) attr = ci.attribute v = self._values[attr] if ci.ismulti(): v.append(sectvalue) elif v is None: self._values[attr] = sectvalue else: raise ZConfig.ConfigurationError( "too many instances of %s section" % repr(ci.sectiontype.name)) def addValue(self, key, value, position): try: realkey = self.type.keytype(key) except ValueError as e: raise ZConfig.DataConversionError(e, key, position) arbkey_info = None for i in range(len(self.type)): k, ci = self.type[i] if k == realkey: break if ci.name == "+" and not ci.issection(): arbkey_info = k, ci else: if arbkey_info is None: raise ZConfig.ConfigurationError( repr(key) + " is not a known key name") k, ci = arbkey_info if ci.issection(): if ci.name: extra = " in %s sections" % repr(self.type.name) else: extra = "" raise ZConfig.ConfigurationError( "%s is not a valid key name%s" % (repr(key), extra)) ismulti = ci.ismulti() attr = ci.attribute assert attr is not None v = self._values[attr] if v is None: if k == '+': v = {} elif ismulti: v = [] self._values[attr] = v elif not ismulti: if k != '+': raise ZConfig.ConfigurationError( repr(key) + " does not support multiple values") elif len(v) == ci.maxOccurs: raise ZConfig.ConfigurationError( "too many values for " + repr(name)) value = ValueInfo(value, position) if k == '+': if ismulti: if realkey in v: v[realkey].append(value) else: v[realkey] = [value] else: if realkey in v: raise ZConfig.ConfigurationError( "too many values for " + repr(key)) v[realkey] = value elif ismulti: v.append(value) else: self._values[attr] = value def createChildMatcher(self, type, name): ci = self.type.getsectioninfo(type.name, name) assert not ci.isabstract() if not ci.isAllowedName(name): raise ZConfig.ConfigurationError( "%s is not an allowed name for %s sections" % (repr(name), repr(ci.sectiontype.name))) return SectionMatcher(ci, type, name, self.handlers) def finish(self): """Check the constraints of the section and convert to an application object.""" values = self._values for key, ci in self.type: if key: key = repr(key) else: key = "section type " + repr(ci.sectiontype.name) assert ci.attribute is not None attr = ci.attribute v = values[attr] if ci.name == '+' and not ci.issection(): # v is a dict if ci.minOccurs > len(v): raise ZConfig.ConfigurationError( "no keys defined for the %s key/value map; at least %d" " must be specified" % (attr, ci.minOccurs)) if v is None and ci.minOccurs: default = ci.getdefault() if default is None: raise ZConfig.ConfigurationError( "no values for %s; %s required" % (key, ci.minOccurs)) else: v = values[attr] = default[:] if ci.ismulti(): if not v: default = ci.getdefault() if isinstance(default, dict): v.update(default) else: v[:] = default if len(v) < ci.minOccurs: raise ZConfig.ConfigurationError( "not enough values for %s; %d found, %d required" % (key, len(v), ci.minOccurs)) if v is None and not ci.issection(): if ci.ismulti(): v = ci.getdefault()[:] else: v = ci.getdefault() values[attr] = v return self.constuct() def constuct(self): values = self._values for name, ci in self.type: assert ci.attribute is not None attr = ci.attribute if ci.ismulti(): if ci.issection(): v = [] for s in values[attr]: if s is not None: st = s.getSectionDefinition() try: s = st.datatype(s) except ValueError as e: raise ZConfig.DataConversionError( e, s, (-1, -1, None)) v.append(s) elif ci.name == '+': v = values[attr] for key, val in v.items(): v[key] = [vi.convert(ci.datatype) for vi in val] else: v = [vi.convert(ci.datatype) for vi in values[attr]] elif ci.issection(): if values[attr] is not None: st = values[attr].getSectionDefinition() try: v = st.datatype(values[attr]) except ValueError as e: raise ZConfig.DataConversionError( e, values[attr], (-1, -1, None)) else: v = None elif name == '+': v = values[attr] if not v: for key, val in ci.getdefault().items(): v[key] = val.convert(ci.datatype) else: for key, val in v.items(): v[key] = val.convert(ci.datatype) else: v = values[attr] if v is not None: v = v.convert(ci.datatype) values[attr] = v if ci.handler is not None: self.handlers.append((ci.handler, v)) return self.createValue() def createValue(self): return SectionValue(self._values, None, self) class SectionMatcher(BaseMatcher): def __init__(self, info, type, name, handlers): if name or info.allowUnnamed(): self.name = name else: raise ZConfig.ConfigurationError( repr(type.name) + " sections may not be unnamed") BaseMatcher.__init__(self, info, type, handlers) def createValue(self): return SectionValue(self._values, self.name, self) class SchemaMatcher(BaseMatcher): def __init__(self, schema): BaseMatcher.__init__(self, schema, schema, []) def finish(self): # Since there's no outer container to call datatype() # for the schema, we convert on the way out. v = BaseMatcher.finish(self) v = self.type.datatype(v) if self.type.handler is not None: self.handlers.append((self.type.handler, v)) return v class SectionValue: """Generic 'bag-of-values' object for a section. Derived classes should always call the SectionValue constructor before attempting to modify self. """ def __init__(self, values, name, matcher): self.__dict__.update(values) self._name = name self._matcher = matcher self._attributes = tuple(values.keys()) def __repr__(self): if self._name: # probably unique for a given config file; more readable than id() name = repr(self._name) else: # identify uniquely name = "at %#x" % id(self) clsname = self.__class__.__name__ return "<%s for %s %s>" % (clsname, self._matcher.type.name, name) def __str__(self): l = [] attrnames = sorted([s for s in self.__dict__.keys() if s[0] != "_"]) for k in attrnames: v = getattr(self, k) l.append('%-40s: %s' % (k, v)) return '\n'.join(l) def getSectionName(self): return self._name def getSectionType(self): return self._matcher.type.name def getSectionDefinition(self): return self._matcher.type def getSectionMatcher(self): return self._matcher def getSectionAttributes(self): return self._attributes ZConfig-3.1.0/ZConfig/schema.py0000644000076500000240000005212312610530667016352 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Parser for ZConfig schemas.""" import os import sys import xml.sax import ZConfig from ZConfig import info from ZConfig import url try: BLANK = unicode('') except NameError: BLANK = '' def parseResource(resource, loader): parser = SchemaParser(loader, resource.url) xml.sax.parse(resource.file, parser) return parser._schema def parseComponent(resource, loader, schema): parser = ComponentParser(loader, resource.url, schema) xml.sax.parse(resource.file, parser) def _srepr(ob): if isinstance(ob, type(BLANK)) and sys.version_info[0] < 3: # drop the leading "u" from a unicode repr return repr(ob)[1:] else: return repr(ob) class BaseParser(xml.sax.ContentHandler): _cdata_tags = "description", "metadefault", "example", "default" _handled_tags = ("import", "abstracttype", "sectiontype", "key", "multikey", "section", "multisection") _allowed_parents = { "description": ["key", "section", "multikey", "multisection", "sectiontype", "abstracttype", "schema", "component"], "example": ["key", "section", "multikey", "multisection"], "metadefault": ["key", "section", "multikey", "multisection"], "default": ["key", "multikey"], "import": ["schema", "component"], "abstracttype": ["schema", "component"], "sectiontype": ["schema", "component"], "key": ["schema", "sectiontype"], "multikey": ["schema", "sectiontype"], "section": ["schema", "sectiontype"], "multisection": ["schema", "sectiontype"], } def __init__(self, loader, url): self._registry = loader.registry self._loader = loader self._basic_key = self._registry.get("basic-key") self._identifier = self._registry.get("identifier") self._cdata = None self._locator = None self._prefixes = [] self._schema = None self._stack = [] self._url = url self._elem_stack = [] # SAX 2 ContentHandler methods def setDocumentLocator(self, locator): self._locator = locator def startElement(self, name, attrs): attrs = dict(attrs) if self._elem_stack: parent = self._elem_stack[-1] if name not in self._allowed_parents: self.error("Unknown tag " + name) if parent not in self._allowed_parents[name]: self.error("%s elements may not be nested in %s elements" % (_srepr(name), _srepr(parent))) elif name != self._top_level: self.error("Unknown document type " + name) self._elem_stack.append(name) if name == self._top_level: if self._schema is not None: self.error("schema element improperly nested") getattr(self, "start_" + name)(attrs) elif name in self._handled_tags: if self._schema is None: self.error(name + " element outside of schema") getattr(self, "start_" + name)(attrs) elif name in self._cdata_tags: if self._schema is None: self.error(name + " element outside of schema") if self._cdata is not None: self.error(name + " element improperly nested") self._cdata = [] self._position = None self._attrs = attrs def characters(self, data): if self._cdata is not None: if self._position is None: self._position = self.get_position() self._cdata.append(data) elif data.strip(): self.error("unexpected non-blank character data: " + repr(data.strip())) def endElement(self, name): del self._elem_stack[-1] if name in self._handled_tags: getattr(self, "end_" + name)() else: data = ''.join(self._cdata).strip() self._cdata = None getattr(self, "characters_" + name)(data) def endDocument(self): if self._schema is None: self.error("no %s found" % self._top_level) # helper methods def get_position(self): if self._locator: return (self._locator.getLineNumber(), self._locator.getColumnNumber(), (self._locator.getSystemId() or self._url)) else: return None, None, self._url def get_handler(self, attrs): v = attrs.get("handler") if v is None: return v else: return self.basic_key(v) def push_prefix(self, attrs): name = attrs.get("prefix") if name: if self._prefixes: convert = self._registry.get("dotted-suffix") else: convert = self._registry.get("dotted-name") try: name = convert(name) except ValueError as err: self.error("not a valid prefix: %s (%s)" % (_srepr(name), str(err))) if name[0] == ".": prefix = self._prefixes[-1] + name else: prefix = name elif self._prefixes: prefix = self._prefixes[-1] else: prefix = '' self._prefixes.append(prefix) def pop_prefix(self): del self._prefixes[-1] def get_classname(self, name): name = str(name) if name.startswith("."): return self._prefixes[-1] + name else: return name def get_datatype(self, attrs, attrkey, default, base=None): if attrkey in attrs: dtname = self.get_classname(attrs[attrkey]) else: convert = getattr(base, attrkey, None) if convert is not None: return convert dtname = default try: return self._registry.get(dtname) except ValueError as e: self.error(e.args[0]) def get_sect_typeinfo(self, attrs, base=None): keytype = self.get_datatype(attrs, "keytype", "basic-key", base) valuetype = self.get_datatype(attrs, "valuetype", "string") datatype = self.get_datatype(attrs, "datatype", "null", base) return keytype, valuetype, datatype def get_required(self, attrs): if "required" in attrs: v = attrs["required"] if v == "yes": return True elif v == "no": return False self.error("value for 'required' must be 'yes' or 'no'") else: return False def get_ordinality(self, attrs): # used by start_multi*() min, max = 0, info.Unbounded if self.get_required(attrs): min = 1 return min, max def get_sectiontype(self, attrs): type = attrs.get("type") if not type: self.error("section must specify type") return self._schema.gettype(type) def get_key_info(self, attrs, element): any, name, attribute = self.get_name_info(attrs, element) if any == '*': self.error(element + " may not specify '*' for name") if not name and any != '+': self.error(element + " name may not be omitted or empty") datatype = self.get_datatype(attrs, "datatype", "string") handler = self.get_handler(attrs) return name or any, datatype, handler, attribute def get_name_info(self, attrs, element, default=None): name = attrs.get("name", default) if not name: self.error(element + " name must be specified and non-empty") aname = attrs.get("attribute") if aname: aname = self.identifier(aname) if aname.startswith("getSection"): # reserved; used for SectionValue methods to get meta-info self.error("attribute names may not start with 'getSection'") if name in ("*", "+"): if not aname: self.error( "container attribute must be specified and non-empty" " when using '*' or '+' for a section name") return name, None, aname else: # run the keytype converter to make sure this is a valid key try: name = self._stack[-1].keytype(name) except ValueError as e: self.error("could not convert key name to keytype: " + str(e)) if not aname: aname = self.basic_key(name) aname = self.identifier(aname.replace('-', '_')) return None, name, aname # schema loading logic def characters_default(self, data): key = self._attrs.get("key") self._stack[-1].adddefault(data, self._position, key) def characters_description(self, data): if self._stack[-1].description is not None: self.error( "at most one may be used for each element") self._stack[-1].description = data def characters_example(self, data): self._stack[-1].example = data def characters_metadefault(self, data): self._stack[-1].metadefault = data def start_import(self, attrs): src = attrs.get("src", "").strip() pkg = attrs.get("package", "").strip() file = attrs.get("file", "").strip() if not (src or pkg): self.error("import must specify either src or package") if src and pkg: self.error("import may only specify one of src or package") if src: if file: self.error("import may not specify file and src") src = url.urljoin(self._url, src) src, fragment = url.urldefrag(src) if fragment: self.error("import src many not include" " a fragment identifier") schema = self._loader.loadURL(src) for n in schema.gettypenames(): self._schema.addtype(schema.gettype(n)) else: if os.path.dirname(file): self.error("file may not include a directory part") pkg = self.get_classname(pkg) src = self._loader.schemaComponentSource(pkg, file) if not self._schema.hasComponent(src): self._schema.addComponent(src) self.loadComponent(src) def loadComponent(self, src): r = self._loader.openResource(src) parser = ComponentParser(self._loader, src, self._schema) try: xml.sax.parse(r.file, parser) finally: r.close() def end_import(self): pass def start_sectiontype(self, attrs): name = attrs.get("name") if not name: self.error("sectiontype name must not be omitted or empty") name = self.basic_key(name) self.push_prefix(attrs) if "extends" in attrs: basename = self.basic_key(attrs["extends"]) base = self._schema.gettype(basename) if base.isabstract(): self.error("sectiontype cannot extend an abstract type") keytype, valuetype, datatype = self.get_sect_typeinfo(attrs, base) sectinfo = self._schema.deriveSectionType( base, name, keytype, valuetype, datatype) else: keytype, valuetype, datatype = self.get_sect_typeinfo(attrs) sectinfo = self._schema.createSectionType( name, keytype, valuetype, datatype) if "implements" in attrs: ifname = self.basic_key(attrs["implements"]) interface = self._schema.gettype(ifname) if not interface.isabstract(): self.error( "type specified by implements is not an abstracttype") interface.addsubtype(sectinfo) self._stack.append(sectinfo) def end_sectiontype(self): self.pop_prefix() self._stack.pop() def start_section(self, attrs): sectiontype = self.get_sectiontype(attrs) handler = self.get_handler(attrs) min = self.get_required(attrs) and 1 or 0 any, name, attribute = self.get_name_info(attrs, "section", "*") if any and not attribute: self.error( "attribute must be specified if section name is '*' or '+'") section = info.SectionInfo(any or name, sectiontype, min, 1, handler, attribute) self._stack[-1].addsection(name, section) self._stack.append(section) def end_section(self): self._stack.pop() def start_multisection(self, attrs): sectiontype = self.get_sectiontype(attrs) min, max = self.get_ordinality(attrs) any, name, attribute = self.get_name_info(attrs, "multisection", "*") if any not in ("*", "+"): self.error("multisection must specify '*' or '+' for the name") handler = self.get_handler(attrs) section = info.SectionInfo(any or name, sectiontype, min, max, handler, attribute) self._stack[-1].addsection(name, section) self._stack.append(section) def end_multisection(self): self._stack.pop() def start_abstracttype(self, attrs): name = attrs.get("name") if not name: self.error("abstracttype name must not be omitted or empty") name = self.basic_key(name) abstype = info.AbstractType(name) self._schema.addtype(abstype) self._stack.append(abstype) def end_abstracttype(self): self._stack.pop() def start_key(self, attrs): name, datatype, handler, attribute = self.get_key_info(attrs, "key") min = self.get_required(attrs) and 1 or 0 key = info.KeyInfo(name, datatype, min, handler, attribute) if "default" in attrs: if min: self.error("required key cannot have a default value") key.adddefault(str(attrs["default"]).strip(), self.get_position()) if name != "+": key.finish() self._stack[-1].addkey(key) self._stack.append(key) def end_key(self): key = self._stack.pop() if key.name == "+": key.computedefault(self._stack[-1].keytype) key.finish() def start_multikey(self, attrs): if "default" in attrs: self.error("default values for multikey must be given using" " 'default' elements") name, datatype, handler, attribute = self.get_key_info(attrs, "multikey") min, max = self.get_ordinality(attrs) key = info.MultiKeyInfo(name, datatype, min, max, handler, attribute) self._stack[-1].addkey(key) self._stack.append(key) def end_multikey(self): multikey = self._stack.pop() if multikey.name == "+": multikey.computedefault(self._stack[-1].keytype) multikey.finish() # datatype conversion wrappers def basic_key(self, s): try: return self._basic_key(s) except ValueError as e: self.error(e[0]) def identifier(self, s): try: return self._identifier(s) except ValueError as e: self.error(e[0]) # exception setup helpers def initerror(self, e): if self._locator is not None: e.colno = self._locator.getColumnNumber() e.lineno = self._locator.getLineNumber() e.url = self._locator.getSystemId() return e def error(self, message): raise self.initerror(ZConfig.SchemaError(message)) class SchemaParser(BaseParser): # needed by startElement() and endElement() _handled_tags = BaseParser._handled_tags + ("schema",) _top_level = "schema" def __init__(self, loader, url, extending_parser=None): BaseParser.__init__(self, loader, url) self._extending_parser = extending_parser self._base_keytypes = [] self._base_datatypes = [] self._descriptions = [] def start_schema(self, attrs): self.push_prefix(attrs) handler = self.get_handler(attrs) keytype, valuetype, datatype = self.get_sect_typeinfo(attrs) if self._extending_parser is None: # We're not being inherited, so we need to create the schema self._schema = info.SchemaType(keytype, valuetype, datatype, handler, self._url, self._registry) else: # Parse into the extending ("subclass") parser's schema self._schema = self._extending_parser._schema self._stack = [self._schema] if "extends" in attrs: sources = attrs["extends"].split() sources.reverse() for src in sources: src = url.urljoin(self._url, src) src, fragment = url.urldefrag(src) if fragment: self.error("schema extends many not include" " a fragment identifier") self.extendSchema(src) # Inherit keytype from bases, if unspecified and not conflicting if self._base_keytypes and "keytype" not in attrs: keytype = self._base_keytypes[0] for kt in self._base_keytypes[1:]: if kt is not keytype: self.error("base schemas have conflicting keytypes," " but no keytype was specified in the" " extending schema") # Inherit datatype from bases, if unspecified and not conflicting if self._base_datatypes and "datatype" not in attrs: datatype = self._base_datatypes[0] for dt in self._base_datatypes[1:]: if dt is not datatype: self.error("base schemas have conflicting datatypes," " but no datatype was specified in the" " extending schema") # Reset the schema types to our own, while we parse the schema body self._schema.keytype = keytype self._schema.valuetype = valuetype self._schema.datatype = datatype # Update base key/datatypes for the "extending" parser if self._extending_parser is not None: self._extending_parser._base_keytypes.append(keytype) self._extending_parser._base_datatypes.append(datatype) def extendSchema(self, src): parser = SchemaParser(self._loader, src, self) r = self._loader.openResource(src) try: xml.sax.parse(r.file, parser) finally: r.close() def end_schema(self): del self._stack[-1] assert not self._stack self.pop_prefix() assert not self._prefixes schema = self._schema if self._extending_parser is None: # Top-level schema: if self._descriptions and not schema.description: # Use the last one, since the base schemas are processed in # reverse order. schema.description = self._descriptions[-1] elif schema.description: self._extending_parser._descriptions.append(schema.description) schema.description = None class ComponentParser(BaseParser): _handled_tags = BaseParser._handled_tags + ("component",) _top_level = "component" def __init__(self, loader, url, schema): BaseParser.__init__(self, loader, url) self._parent = schema def characters_description(self, data): if self._stack: self._stack[-1].description = data def start_key(self, attrs): self._check_not_toplevel("key") BaseParser.start_key(self, attrs) def start_multikey(self, attrs): self._check_not_toplevel("multikey") BaseParser.start_multikey(self, attrs) def start_section(self, attrs): self._check_not_toplevel("section") BaseParser.start_section(self, attrs) def start_multisection(self, attrs): self._check_not_toplevel("multisection") BaseParser.start_multisection(self, attrs) def start_component(self, attrs): self._schema = self._parent self.push_prefix(attrs) def end_component(self): self.pop_prefix() def _check_not_toplevel(self, what): if not self._stack: self.error("cannot define top-level %s in a schema %s" % (what, self._top_level)) ZConfig-3.1.0/ZConfig/schemaless.py0000644000076500000240000000612512610530667017242 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """\ Support for working with ZConfig data without a schema. """ __docformat__ = "reStructuredText" import ZConfig.cfgparser def loadConfigFile(file, url=None): c = Context() Parser(Resource(file, url), c).parse(c.top) return c.top class Resource: def __init__(self, file, url=''): self.file, self.url = file, url class Section(dict): imports = () def __init__(self, type='', name='', data=None, sections=None): dict.__init__(self) if data: self.update(data) self.sections = sections or [] self.type, self.name = type, name def addValue(self, key, value, *args): if key in self: self[key].append(value) else: self[key] = [value] def __str__(self, pre=''): result = [] if self.imports: for pkgname in self.imports: result.append('%import ' + pkgname) result.append('') if self.type: if self.name: start = '%s<%s %s>' % (pre, self.type, self.name) else: start = '%s<%s>' % (pre, self.type) result.append(start) pre += ' ' lst = sorted(self.items()) for name, values in lst: for value in values: result.append('%s%s %s' % (pre, name, value)) if self.sections and self: result.append('') for section in self.sections: result.append(section.__str__(pre)) if self.type: pre = pre[:-2] result.append('%s' % (pre, self.type)) result.append('') result = '\n'.join(result).rstrip() if not pre: result += '\n' return result class Context: def __init__(self): self.top = Section() self.sections = [] def startSection(self, container, type, name): newsec = Section(type, name) container.sections.append(newsec) return newsec def endSection(self, container, type, name, newsect): pass def importSchemaComponent(self, pkgname): if pkgname not in self.top.imports: self.top.imports += (pkgname, ) def includeConfiguration(self, section, newurl, defines): raise NotImplementedError('includes are not supported') class Parser(ZConfig.cfgparser.ZConfigParser): def handle_define(self, section, rest): raise NotImplementedError('defines are not supported') ZConfig-3.1.0/ZConfig/schemaless.txt0000644000076500000240000001552612610530667017436 0ustar do3ccstaff00000000000000================================= Using ZConfig data without schema ================================= Sometimes it's useful to use ZConfig configuration data without a schema. This is most interesting when assembling a configuration from fragments, as some buildout recipes do. This is not recommended for general application use. The ``ZConfig.schemaless`` module provides some support for working without schema. Something things are not (currently) supported, including the %define and %include directives. The %import directive is supported. This module provides basic support for loading configuration, inspecting and modifying it, and re-serializing the result. >>> from ZConfig import schemaless There is a single function which loads configuration data from a file open for reading. Let's take a look at this, and what it returns:: >>> config_text = ''' ... ... some-key some-value ... ... some-key another-value ... ...
... key1 value1.1 ... key1 value1.2 ... key2 value2 ... ... ... another key ... another value ... ...
... ... another-key whee! ... ... ... ... nothing here ... ... ... ''' >>> try: ... import StringIO ... except ImportError: ... import io as StringIO >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) The `config` object is a mapping from top-level keys to lists of values:: >>> config["some-key"] ['some-value', 'another-value'] >>> config["another-key"] ['whee!'] >>> config["no-such-key-in-the-config"] Traceback (most recent call last): KeyError: 'no-such-key-in-the-config' >>> lst = list(config) >>> lst.sort() >>> lst ['another-key', 'some-key'] There is also a ``sections`` attribute that lists child sections:: >>> len(config.sections) 2 Let's take a look at one of the sections. Like the top-level configuration, the section maps keys >>> section = config.sections[0] >>> section["key1"] ['value1.1', 'value1.2'] >>> section["key2"] ['value2'] >>> section["no-such-key-in-the-config"] Traceback (most recent call last): KeyError: 'no-such-key-in-the-config' >>> lst = list(section) >>> lst.sort() >>> lst ['key1', 'key2'] Child sections are again available via the ``sections`` attribute:: >>> len(section.sections) 1 In addition, the section has ``type`` and ``name`` attributes that record the type and name of the section as ZConfig understands them:: >>> section.type 'section' >>> print(section.name) None Let's look at the named section from our example, so we can see the name:: >>> section = config.sections[1] >>> section.type 'another' >>> section.name 'named' We can also mutate the configuration, adding new keys and values as desired:: >>> config["new-key"] = ["new-value-1", "new-value-2"] >>> config["some-key"].append("third-value") New sections can also be added:: >>> section = schemaless.Section("sectiontype", "my-name") >>> section["key"] = ["value"] >>> config.sections.insert(1, section) The configuration can be re-serialized using ``str()``:: >>> print(str(config)) another-key whee! new-key new-value-1 new-key new-value-2 some-key some-value some-key another-value some-key third-value
key1 value1.1 key1 value1.2 key2 value2 another key another value
key value nothing here Note that some adjustments have been made: - key/value pairs come before child sections - keys are sorted at each level - blank lines are removed, with new blank lines inserted to preserve some semblance of readability These are all presentation changes, but not essential changes to the configuration data. The ordering of sections is not modified in rendering, nor are the values for a single key re-ordered within a section or top-level configuration. Support for %import ------------------- Imports are supported, and are re-ordered in much the same way that other elements of a configuration are:: >>> config_text = ''' ... ... %import some.package ... ...
... ... %import another.package ... ... ... some value ... ... ...
... ... some-key some-value ... ... ''' >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) >>> print(config) %import some.package %import another.package some-key some-value
some value
The imports are also available as the ``imports`` attribute of the configuration object:: >>> config.imports ('some.package', 'another.package') Multiple imports of the same name are removed:: >>> config_text = ''' ... ... %import some.package ... %import another.package ... %import some.package ... ... ''' >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) >>> print(config) %import some.package %import another.package >>> config.imports ('some.package', 'another.package') Limitations ----------- There are some limitations of handling ZConfig-based configurations using the ``ZConfig.schemaless`` module. Some of these are implementation issues, and may be corrected in the future: - %define is not supported. - %include is not supported. Others are a function of not processing the schema, and can't easily be avoided: - normalization of keys based on keytypes specified in the or elements of the schema if not performed. If the transformation of a key might affect the behavior controlled by the resulting configuration, the generated configuration may not be equivalent. Examples of this are unusual, but exist. Limitations related to the non-processing of the schema cannot be detected by the ``ZConfig.schemaless``, so no errors are reported in these situations. For the strictly syntactic limitations, we do get errors when the input data requires they be supported. Let's look at both the %define and %include handling. When %define is used in the input configuration, an exception is raised when loading the configuration:: >>> config_text = ''' ... ... %define somename somevalue ... ... ''' >>> schemaless.loadConfigFile(StringIO.StringIO(config_text)) Traceback (most recent call last): NotImplementedError: defines are not supported A similar exception is raised for %include:: >>> config_text = ''' ... ... %include some/other/file.conf ... ... ''' >>> schemaless.loadConfigFile(StringIO.StringIO(config_text)) Traceback (most recent call last): NotImplementedError: includes are not supported ZConfig-3.1.0/ZConfig/substitution.py0000644000076500000240000000653312610530667017672 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Substitution support for ZConfig values.""" import os import ZConfig def substitute(s, mapping): """Interpolate variables from `mapping` into `s`.""" if "$" in s: result = '' rest = s while rest: p, name, namecase, rest, vtype = _split(rest) result += p if name: v = None if vtype == 'define': v = mapping.get(name) if vtype == 'env': v = os.getenv(namecase) if v is None: raise ZConfig.SubstitutionReplacementError(s, namecase) result += v return result else: return s def isname(s): """Return True iff s is a valid substitution name.""" m = _name_match(s) if m: return m.group() == s else: return False def _split(s): # Return a four tuple: prefix, name, namecase, suffix # - prefix is text that can be used literally in the result (may be '') # - name is a referenced name, or None # - namecase is the name with case preserved # - suffix is trailling text that may contain additional references # (may be '' or None) if "$" in s: i = s.find("$") c = s[i+1:i+2] if c == "": raise ZConfig.SubstitutionSyntaxError( "illegal lone '$' at end of source") if c == "$": return s[:i+1], None, None, s[i+2:], None prefix = s[:i] vtype = 'define' if c == "{": m = _name_match(s, i + 2) if not m: raise ZConfig.SubstitutionSyntaxError( "'${' not followed by name") name = m.group(0) i = m.end() + 1 if not s.startswith("}", i - 1): raise ZConfig.SubstitutionSyntaxError( "'${%s' not followed by '}'" % name) elif c == "(": m = _name_match(s, i + 2) if not m: raise ZConfig.SubstitutionSyntaxError( "'$(' not followed by name") name = m.group(0) i = m.end() + 1 if not s.startswith(")", i - 1): raise ZConfig.SubstitutionSyntaxError( "'$(%s' not followed by ')'" % name) vtype = 'env' else: m = _name_match(s, i+1) if not m: raise ZConfig.SubstitutionSyntaxError( "'$' not followed by '$' or name") name = m.group(0) i = m.end() return prefix, name.lower(), name, s[i:], vtype else: return s, None, None, None, None import re _name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match del re ZConfig-3.1.0/ZConfig/tests/0000755000076500000240000000000012610530670015671 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/__init__.py0000644000076500000240000000137212610530667020013 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the configuration data structures and loader. $Id: __init__.py,v 1.2 2003/01/03 21:05:56 fdrake Exp $ """ ZConfig-3.1.0/ZConfig/tests/foosample.zip0000644000076500000240000000305512610530667020413 0ustar do3ccstaff00000000000000PKfg3I&&foo/__init__.pySV,VH,JM./Tr*K2 SPKyg3H1:Yzfoo/__init__.pyc˵t_s20` H(I9)I)%z9yffiy%z% AAPKsg3I&&foo/sample/__init__.pySV,VH,JM./Tr*K2 SPKsg3j|foo/sample/datatypes.pyK 0 D: S${#I),rǨ`wf̻#X⽽RާFWCCx:odcK)hsf NEPKsg3ى(pfoo/sample/component.xmlMA1E= йK32(Xf4ͤ:'Up7[F`\S7S?:V, 5(;zEy(|(̓Ɣ PKyg3Br`foo/sample/__init__.pycp59 YD CHO$F̜̼ L3 3t|܂T̼̒xy PKyg3Cwpfoo/sample/datatypes.pycUj0 dJ`7z( z!٩n0$MB{ŽȞ`i3C_~~Sw @;%H]a8ЏC{lޑn4%F?aNX˰bFMeʷ['䔲gϪ&#+pE{|ìGzun&?}m6W 4憷t_+kNu*U$ Y1gdo9$PKfg3I&&foo/__init__.pyPKyg3H1:YzSfoo/__init__.pycPKsg3I&&foo/sample/__init__.pyPKsg3j|4foo/sample/datatypes.pyPKsg3ى(pfoo/sample/component.xmlPKyg3Br`yfoo/sample/__init__.pycPKyg3Cwpfoo/sample/datatypes.pycPKBZConfig-3.1.0/ZConfig/tests/input/0000755000076500000240000000000012610530670017030 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/input/base-datatype1.xml0000644000076500000240000000014212610530667022361 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/base-datatype2.xml0000644000076500000240000000014212610530667022362 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/base-keytype1.xml0000644000076500000240000000010612610530667022240 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/base-keytype2.xml0000644000076500000240000000014012610530667022237 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/base.xml0000644000076500000240000000024112610530667020467 0ustar do3ccstaff00000000000000 base description ZConfig-3.1.0/ZConfig/tests/input/include.conf0000644000076500000240000000007012610530667021325 0ustar do3ccstaff00000000000000var2 value2 %include simple.conf var3 value3 var4 $name ZConfig-3.1.0/ZConfig/tests/input/inner.conf0000644000076500000240000000005212610530667021015 0ustar do3ccstaff00000000000000refouter $outervar %define innervar inner ZConfig-3.1.0/ZConfig/tests/input/library.xml0000644000076500000240000000023512610530667021224 0ustar do3ccstaff00000000000000 Sample library of reusable data types. ZConfig-3.1.0/ZConfig/tests/input/logger.xml0000644000076500000240000000062412610530667021041 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/outer.conf0000644000076500000240000000007612610530667021046 0ustar do3ccstaff00000000000000%define outervar outer %include inner.conf refinner $innervar ZConfig-3.1.0/ZConfig/tests/input/simple.conf0000644000076500000240000000067012610530667021201 0ustar do3ccstaff00000000000000empty var1 abc int-var 12 float-var 12.02 neg-int -2 true-var-1 true true-var-2 on true-var-3 yes false-var-1 false false-var-2 off false-var-3 no list-1 list-2 abc list-3 abc def ghi list-4 [ what now? ] # These test the %define mechanism: %define dollars $$$$ %define empty %define name value %define twowords two words getname $name getnametwice $name${name} getdollars $dollars getempty x${empty}y getwords abc $twowords def ZConfig-3.1.0/ZConfig/tests/input/simple.xml0000644000076500000240000000166712610530667021063 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/input/simplesections.conf0000644000076500000240000000063112610530667022746 0ustar do3ccstaff00000000000000var foo var-0 foo-0
var bar var-one splat
var-1 foo-1
var spam var-two stuff
var-2 foo-2
var quack! var-three yet
var-3 foo-3 # An anonymous empty section:
var-4 foo-4 # A fairly trivial section: var triv var-5 foo-5 # A minimal section: var-6 foo-6 ZConfig-3.1.0/ZConfig/tests/input/simplesections.xml0000644000076500000240000000120112610530667022613 0ustar do3ccstaff00000000000000
ZConfig-3.1.0/ZConfig/tests/library/0000755000076500000240000000000012610530670017335 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/library/__init__.py0000644000076500000240000000002712610530667021453 0ustar do3ccstaff00000000000000# Make this a package. ZConfig-3.1.0/ZConfig/tests/library/README.txt0000644000076500000240000000013012610530667021033 0ustar do3ccstaff00000000000000This is a sample library of configuration schema components. This is used for testing. ZConfig-3.1.0/ZConfig/tests/library/thing/0000755000076500000240000000000012610530670020446 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/library/thing/__init__.py0000644000076500000240000000150712610530667022570 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Example of a package that extends its __path__. $Id: __init__.py,v 1.2 2003/10/03 17:11:33 fdrake Exp $ """ import os here = os.path.dirname(__file__) __path__.append(os.path.join(here, "extras")) ZConfig-3.1.0/ZConfig/tests/library/thing/component.xml0000644000076500000240000000053012610530667023176 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/library/thing/extras/0000755000076500000240000000000012610530670021754 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/library/thing/extras/extras.xml0000644000076500000240000000015012610530667024006 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/library/widget/0000755000076500000240000000000012610530670020620 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/library/widget/__init__.py0000644000076500000240000000002712610530667022736 0ustar do3ccstaff00000000000000# Make this a package. ZConfig-3.1.0/ZConfig/tests/library/widget/component.xml0000644000076500000240000000040012610530667023344 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/library/widget/extra.xml0000644000076500000240000000014712610530667022475 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/support.py0000644000076500000240000000514012610530667017765 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support code shared among the tests.""" import os import ZConfig from ZConfig.loader import ConfigLoader from ZConfig.url import urljoin try: import StringIO except ImportError: # Python 3 support. import io as StringIO try: from urllib import pathname2url except ImportError: # Python 3 support. from urllib.request import pathname2url try: __file__ except NameError: import sys __file__ = sys.argv[0] d = os.path.abspath(os.path.join(os.path.dirname(__file__), "input")) CONFIG_BASE = "file://%s/" % pathname2url(d) class TestHelper: """Utility methods which can be used with the schema support.""" # Not derived from unittest.TestCase; some test runners seem to # think that means this class contains tests. def load_both(self, schema_url, conf_url): schema = self.load_schema(schema_url) conf = self.load_config(schema, conf_url) return schema, conf def load_schema(self, relurl): self.url = urljoin(CONFIG_BASE, relurl) self.schema = ZConfig.loadSchema(self.url) self.assertTrue(self.schema.issection()) return self.schema def load_schema_text(self, text, url=None): sio = StringIO.StringIO(text) self.schema = ZConfig.loadSchemaFile(sio, url) return self.schema def load_config(self, schema, conf_url, num_handlers=0): conf_url = urljoin(CONFIG_BASE, conf_url) loader = self.create_config_loader(schema) self.conf, self.handlers = loader.loadURL(conf_url) self.assertEqual(len(self.handlers), num_handlers) return self.conf def load_config_text(self, schema, text, num_handlers=0, url=None): sio = StringIO.StringIO(text) loader = self.create_config_loader(schema) self.conf, self.handlers = loader.loadFile(sio, url) self.assertEqual(len(self.handlers), num_handlers) return self.conf def create_config_loader(self, schema): return ConfigLoader(schema) ZConfig-3.1.0/ZConfig/tests/test_cfgimports.py0000644000076500000240000000423012610530667021464 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the %import mechanism. """ import unittest import ZConfig import ZConfig.tests.support try: from StringIO import StringIO except ImportError: # Python 3 support. from io import StringIO class TestImportFromConfiguration( ZConfig.tests.support.TestHelper, unittest.TestCase): def test_simple_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) config, _ = loader.loadFile( StringIO("%import ZConfig.tests.library.widget\n")) # make sure we now have a "private" schema object; the only # way to get it is from the loader itself self.assertTrue(schema is not loader.schema) # make sure component types are only found on the private schema: loader.schema.gettype("widget-b") self.assertRaises(ZConfig.SchemaError, schema.gettype, "widget-b") def test_repeated_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) config, _ = loader.loadFile( StringIO("%import ZConfig.tests.library.widget\n" "%import ZConfig.tests.library.widget\n")) def test_missing_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) self.assertRaises(ZConfig.SchemaError, loader.loadFile, StringIO("%import ZConfig.tests.missing\n")) def test_suite(): return unittest.makeSuite(TestImportFromConfiguration) ZConfig-3.1.0/ZConfig/tests/test_cmdline.py0000644000076500000240000001501312610530667020723 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the command-line integration.""" import unittest import ZConfig import ZConfig.tests.support from ZConfig.cmdline import ExtendedConfigLoader class CommandLineTest(ZConfig.tests.support.TestHelper, unittest.TestCase): def create_config_loader(self, schema): loader = ExtendedConfigLoader(schema) for item in self.clopts: loader.addOption(*item) return loader def test_loading(self): schema = self.load_schema_text("""\
""") self.clopts = [("mykey=splat!", None), ("section/innerkey=spoogey", None)] bag = self.create_config_loader(schema).cook() # Test a variety of queries on the OptionBag: self.assertTrue("mykey" in bag) self.assertTrue("another" not in bag) self.assertEqual(bag.get_section_info("st", None), None) self.assertEqual(bag.get_section_info("st", "missing-sect"), None) # Consume everything in the OptionBag: L = bag.get_key("mykey") s, pos = L[0] self.assertEqual(len(L), 1) self.assertEqual(s, "splat!") bag2 = bag.get_section_info("st", "section") self.assertTrue("innerkey" in bag2) self.assertTrue("another" not in bag2) L = bag2.get_key("innerkey") s, pos = L[0] self.assertEqual(len(L), 1) self.assertEqual(s, "spoogey") # "Finish" to make sure everything has been consumed: bag2.finish() bag.finish() def test_named_sections(self): schema = self.load_schema_text("""\
""") self.clopts = [("foo/k1=v1", None), ("bar/k2=v2", ("someurl", 2, 3))] bag = self.create_config_loader(schema).cook() foo = bag.get_section_info("st2", "foo") bar = bag.get_section_info("st2", "bar") bag.finish() self.assertEqual(bar.get_key("k2"), [("v2", ("someurl", 2, 3))]) bar.finish() # Ignore foo for now; it's not really important *when* it fails. simple_schema = None def get_simple_schema(self): if self.simple_schema is None: self.__class__.simple_schema = self.load_schema_text("""\ """) return self.simple_schema def test_reading_config(self): self.clopts = [("k1=stringvalue", None), ("k2=12", None)] schema = self.get_simple_schema() conf = self.load_config_text(schema, """\ k0 stuff k1 replaced-stuff k2 42 """) self.assertEqual(conf.k0, "stuff") self.assertEqual(conf.k1, "stringvalue") self.assertEqual(conf.k2, 12) self.assertEqual(conf.k3, 19) def test_unknown_key(self): self.clopts = [("foo=bar", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_too_many_keys(self): self.clopts = [("k1=v1", None), ("k1=v2", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_bad_datatype(self): self.clopts = [("k2=42.0", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.DataConversionError, self.load_config_text, schema, "") def test_without_clopts(self): self.clopts = [] schema = self.get_simple_schema() conf = self.load_config_text(schema, "k3 42") self.assertEqual(conf.k0, None) self.assertEqual(conf.k1, None) self.assertEqual(conf.k2, None) self.assertEqual(conf.k3, 42) def test_section_contents(self): schema = self.load_schema_text("""\ k3-v1 k3-v2 k3-v3
""") self.clopts = [("s1/k1=foo", None), ("s2/k3=value1", None), ("s2/k3=value2", None), ("s1/k2=99", None), ("s2/k3=value3", None), ("s2/k3=value4", None), ] conf = self.load_config_text(schema, "\n") self.assertEqual(conf.s1.k1, "foo") self.assertEqual(conf.s1.k2, 99) self.assertEqual(conf.s1.k3, ["k3-v1", "k3-v2", "k3-v3"]) self.assertEqual(conf.s2.k1, None) self.assertEqual(conf.s2.k2, 3) self.assertEqual(conf.s2.k3, ["value1", "value2", "value3", "value4"]) def test_suite(): return unittest.makeSuite(CommandLineTest) if __name__ == "__main__": unittest.main(defaultTest="test_suite") ZConfig-3.1.0/ZConfig/tests/test_config.py0000644000076500000240000001510712610530667020561 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the configuration data structures and loader.""" import os import tempfile import unittest import ZConfig from ZConfig.tests.support import CONFIG_BASE try: import StringIO as StringIO except ImportError: # Python 3 support. import io as StringIO class ConfigurationTestCase(unittest.TestCase): schema = None def get_schema(self): if self.schema is None: ConfigurationTestCase.schema = ZConfig.loadSchema( CONFIG_BASE + "simple.xml") return self.schema def load(self, relurl, context=None): url = CONFIG_BASE + relurl self.conf, self.handlers = ZConfig.loadConfig(self.get_schema(), url) conf = self.conf #self.assertEqual(conf.url, url) self.assertTrue(conf.getSectionName() is None) self.assertTrue(conf.getSectionType() is None) #self.assertTrue(conf.delegate is None) return conf def loadtext(self, text): sio = StringIO.StringIO(text) return self.loadfile(sio) def loadfile(self, file): schema = self.get_schema() self.conf, self.handlers = ZConfig.loadConfigFile(schema, file) return self.conf def check_simple_gets(self, conf): self.assertEqual(conf.empty, '') self.assertEqual(conf.int_var, 12) self.assertEqual(conf.neg_int, -2) self.assertEqual(conf.float_var, 12.02) self.assertEqual(conf.var1, 'abc') self.assertTrue(conf.true_var_1) self.assertTrue(conf.true_var_2) self.assertTrue(conf.true_var_3) self.assertTrue(not conf.false_var_1) self.assertTrue(not conf.false_var_2) self.assertTrue(not conf.false_var_3) self.assertEqual(conf.list_1, []) self.assertEqual(conf.list_2, ['abc']) self.assertEqual(conf.list_3, ['abc', 'def', 'ghi']) self.assertEqual(conf.list_4, ['[', 'what', 'now?', ']']) def test_simple_gets(self): conf = self.load("simple.conf") self.check_simple_gets(conf) def test_type_errors(self): Error = ZConfig.DataConversionError raises = self.assertRaises raises(Error, self.loadtext, "int-var true") raises(Error, self.loadtext, "float-var true") raises(Error, self.loadtext, "neg-int false") raises(Error, self.loadtext, "true-var-1 0") raises(Error, self.loadtext, "true-var-1 1") raises(Error, self.loadtext, "true-var-1 -1") def test_simple_sections(self): self.schema = ZConfig.loadSchema(CONFIG_BASE + "simplesections.xml") conf = self.load("simplesections.conf") self.assertEqual(conf.var, "foo") # check each interleaved position between sections for c in "0123456": self.assertEqual(getattr(conf, "var_" +c), "foo-" + c) sect = [sect for sect in conf.sections if sect.getSectionName() == "name"][0] self.assertEqual(sect.var, "bar") self.assertEqual(sect.var_one, "splat") self.assertTrue(sect.var_three is None) sect = [sect for sect in conf.sections if sect.getSectionName() == "delegate"][0] self.assertEqual(sect.var, "spam") self.assertEqual(sect.var_two, "stuff") self.assertTrue(sect.var_three is None) def test_include(self): conf = self.load("include.conf") self.assertEqual(conf.var1, "abc") self.assertEqual(conf.var2, "value2") self.assertEqual(conf.var3, "value3") self.assertEqual(conf.var4, "value") def test_includes_with_defines(self): self.schema = ZConfig.loadSchemaFile(StringIO.StringIO("""\ """)) conf = self.load("outer.conf") self.assertEqual(conf.refinner, "inner") self.assertEqual(conf.refouter, "outer") def test_define(self): conf = self.load("simple.conf") self.assertEqual(conf.getname, "value") self.assertEqual(conf.getnametwice, "valuevalue") self.assertEqual(conf.getdollars, "$$") self.assertEqual(conf.getempty, "xy") self.assertEqual(conf.getwords, "abc two words def") def test_define_errors(self): self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define\n") self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define abc-def\n") self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define a value\n%define a other\n") # doesn't raise if value is equal self.loadtext("%define a value\n%define a value\n") def test_fragment_ident_disallowed(self): self.assertRaises(ZConfig.ConfigurationError, self.load, "simplesections.conf#another") def test_load_from_fileobj(self): sio = StringIO.StringIO("%define name value\n" "getname x $name y \n") cf = self.loadfile(sio) self.assertEqual(cf.getname, "x value y") def test_load_from_abspath(self): fn = self.write_tempfile() try: self.check_load_from_path(fn) finally: os.unlink(fn) def test_load_from_relpath(self): fn = self.write_tempfile() dir, name = os.path.split(fn) pwd = os.getcwd() try: os.chdir(dir) self.check_load_from_path(name) finally: os.chdir(pwd) os.unlink(fn) def write_tempfile(self): fn = tempfile.mktemp() fp = open(fn, "w") fp.write("var1 value\n") fp.close() return fn def check_load_from_path(self, path): schema = self.get_schema() ZConfig.loadConfig(schema, path) def test_suite(): return unittest.makeSuite(ConfigurationTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') ZConfig-3.1.0/ZConfig/tests/test_cookbook.py0000644000076500000240000000455212610530667021124 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of examples from the online cookbook, so we don't break them down the road. Unless we really mean to. The ZConfig Cookbook is available online at: http://dev.zope.org/Zope3/ZConfig """ import ZConfig.tests.support import unittest def basic_key_mapping_password_to_passwd(key): # Lower-case the key since that's what basic-key does: key = key.lower() # Now map password to passwd: if key == "password": key = "passwd" return key def user_info_conversion(section): return section class CookbookTestCase(ZConfig.tests.support.TestHelper, unittest.TestCase): def test_rewriting_key_names(self): schema = self.load_schema_text("""
""" % __name__) config = self.load_config_text(schema, """\ USERID 42 USERNAME foouser PASSWORD yeah-right """) self.assertEqual(config.userinfo.userid, 42) self.assertEqual(config.userinfo.username, "foouser") self.assertEqual(config.userinfo.passwd, "yeah-right") self.assertTrue(not hasattr(config.userinfo, "password")) def test_suite(): return unittest.makeSuite(CookbookTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") ZConfig-3.1.0/ZConfig/tests/test_datatypes.py0000644000076500000240000003573712610530667021325 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of standard ZConfig datatypes.""" import os import sys import shutil import socket import datetime import tempfile import unittest import ZConfig.datatypes try: here = __file__ except NameError: here = sys.argv[0] here = os.path.abspath(here) try: unicode except NameError: have_unicode = False else: have_unicode = True class DatatypeTestCase(unittest.TestCase): types = ZConfig.datatypes.Registry() def test_datatype_basickey(self): convert = self.types.get("basic-key") eq = self.assertEqual raises = self.assertRaises eq(convert("abc"), "abc") eq(convert("ABC_DEF.123"), "abc_def.123") eq(convert("Abc-Def-456"), "abc-def-456") eq(convert("Abc.Def"), "abc.def") raises(ValueError, convert, "_abc") raises(ValueError, convert, "-abc") raises(ValueError, convert, "123") raises(ValueError, convert, "") def test_datatype_boolean(self): convert = self.types.get("boolean") check = self.assertTrue raises = self.assertRaises check(convert("on")) check(convert("true")) check(convert("yes")) check(not convert("off")) check(not convert("false")) check(not convert("no")) raises(ValueError, convert, '0') raises(ValueError, convert, '1') raises(ValueError, convert, '') raises(ValueError, convert, 'junk') def test_datatype_float(self): convert = self.types.get("float") eq = self.assertEqual raises = self.assertRaises eq(convert("1"), 1.0) self.assertTrue(type(convert(1)) is type(1.0)) eq(convert("1.1"), 1.1) eq(convert("50.50"), 50.50) eq(convert("-50.50"), -50.50) eq(convert(0), 0.0) eq(convert("0"), 0.0) eq(convert("-0"), 0.0) eq(convert("0.0"), 0.0) raises(ValueError, convert, "junk") raises(ValueError, convert, "0x234.1.9") raises(ValueError, convert, "0.9-") # These are not portable representations; make sure they are # disallowed everywhere for consistency. raises(ValueError, convert, "inf") raises(ValueError, convert, "-inf") raises(ValueError, convert, "nan") if have_unicode: raises(ValueError, convert, unicode("inf")) raises(ValueError, convert, unicode("-inf")) raises(ValueError, convert, unicode("nan")) def test_datatype_identifier(self): convert = self.types.get("identifier") raises = self.assertRaises self.check_names(convert) self.check_never_namelike(convert) raises(ValueError, convert, ".abc") def check_names(self, convert): eq = self.assert_ascii_equal eq(convert, "AbcDef") eq(convert, "a________") eq(convert, "abc_def") eq(convert, "int123") eq(convert, "_abc") eq(convert, "_123") eq(convert, "__dict__") def assert_ascii_equal(self, convert, value): v = convert(value) self.assertEqual(v, value) self.assertTrue(isinstance(v, str)) if have_unicode: unicode_value = unicode(value) v = convert(unicode_value) self.assertEqual(v, value) self.assertTrue(isinstance(v, str)) def check_never_namelike(self, convert): raises = self.assertRaises raises(ValueError, convert, "2345") raises(ValueError, convert, "23.45") raises(ValueError, convert, ".45") raises(ValueError, convert, "23.") raises(ValueError, convert, "abc.") raises(ValueError, convert, "-abc") raises(ValueError, convert, "-123") raises(ValueError, convert, "abc-") raises(ValueError, convert, "123-") raises(ValueError, convert, "-") raises(ValueError, convert, ".") raises(ValueError, convert, "&%$*()") raises(ValueError, convert, "") def test_datatype_dotted_name(self): convert = self.types.get("dotted-name") raises = self.assertRaises self.check_names(convert) self.check_dotted_names(convert) self.check_never_namelike(convert) raises(ValueError, convert, "abc.") raises(ValueError, convert, ".abc.") raises(ValueError, convert, "abc.def.") raises(ValueError, convert, ".abc.def.") raises(ValueError, convert, ".abc.def") def test_datatype_dotted_suffix(self): convert = self.types.get("dotted-suffix") eq = self.assert_ascii_equal raises = self.assertRaises self.check_names(convert) self.check_dotted_names(convert) self.check_never_namelike(convert) eq(convert, ".a") eq(convert, ".a.b") eq(convert, ".a.b.c.d.e.f.g.h.i.j.k.l.m.n.o") raises(ValueError, convert, "abc.") raises(ValueError, convert, ".abc.") raises(ValueError, convert, "abc.def.") raises(ValueError, convert, ".abc.def.") def check_dotted_names(self, convert): eq = self.assert_ascii_equal eq(convert, "abc.def") eq(convert, "abc.def.ghi") eq(convert, "a.d.g.g.g.g.g.g.g") def test_datatype_inet_address(self): convert = self.types.get("inet-address") eq = self.assertEqual defhost = ZConfig.datatypes.DEFAULT_HOST eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert("Host.Example.Com:0"), ("host.example.com", 0)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("[::1]:80"), ("::1", 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) eq(convert("2001::ABCD"), ("2001::abcd", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_inet_binding_address(self): convert = self.types.get("inet-binding-address") eq = self.assertEqual defhost = "" eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_inet_connection_address(self): convert = self.types.get("inet-connection-address") eq = self.assertEqual defhost = "127.0.0.1" eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_integer(self): convert = self.types.get("integer") eq = self.assertEqual raises = self.assertRaises eq(convert('-100'), -100) eq(convert('-1'), -1) eq(convert('-0'), 0) eq(convert('0'), 0) eq(convert('1'), 1) eq(convert('100'), 100) eq(convert('65535'), 65535) eq(convert('65536'), 65536) raises(ValueError, convert, 'abc') raises(ValueError, convert, '-0xabc') raises(ValueError, convert, '') raises(ValueError, convert, '123 456') raises(ValueError, convert, '123-') def test_datatype_locale(self): convert = self.types.get("locale") # Python supports "C" even when the _locale module is not available self.assertEqual(convert("C"), "C") self.assertRaises(ValueError, convert, "locale-does-not-exist") def test_datatype_port(self): convert = self.types.get("port-number") eq = self.assertEqual raises = self.assertRaises raises(ValueError, convert, '-1') eq(convert('0'), 0) eq(convert('1'), 1) eq(convert('80'), 80) eq(convert('1023'), 1023) eq(convert('1024'), 1024) eq(convert('60000'), 60000) eq(convert('65535'), 0xffff) raises(ValueError, convert, '65536') def test_datatype_socket_address(self): convert = self.types.get("socket-address") eq = self.assertEqual AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 defhost = ZConfig.datatypes.DEFAULT_HOST def check(value, family, address, self=self, convert=convert): a = convert(value) self.assertEqual(a.family, family) self.assertEqual(a.address, address) check("Host.Example.Com:80", AF_INET, ("host.example.com", 80)) check(":80", AF_INET, (defhost, 80)) check("80", AF_INET, (defhost, 80)) check("host.EXAMPLE.com", AF_INET, ("host.example.com",None)) check("::1", AF_INET6,("::1", None)) check("[::]:80", AF_INET6,("::", 80)) a1 = convert("/tmp/var/@345.4") a2 = convert("/tmp/var/@345.4:80") self.assertEqual(a1.address, "/tmp/var/@345.4") self.assertEqual(a2.address, "/tmp/var/@345.4:80") if hasattr(socket, "AF_UNIX"): self.assertEqual(a1.family, socket.AF_UNIX) self.assertEqual(a2.family, socket.AF_UNIX) else: self.assertTrue(a1.family is None) self.assertTrue(a2.family is None) def test_ipaddr_or_hostname(self): convert = self.types.get('ipaddr-or-hostname') eq = self.assertEqual raises = self.assertRaises eq(convert('hostname'), 'hostname') eq(convert('hostname.com'), 'hostname.com') eq(convert('www.hostname.com'), 'www.hostname.com') eq(convert('HOSTNAME'), 'hostname') eq(convert('HOSTNAME.COM'), 'hostname.com') eq(convert('WWW.HOSTNAME.COM'), 'www.hostname.com') eq(convert('127.0.0.1'), '127.0.0.1') eq(convert('::1'), '::1') eq(convert('2001:DB8:1234:4567:89AB:cdef:0:1'), '2001:db8:1234:4567:89ab:cdef:0:1') eq(convert('2001:DB8:1234:4567::10.11.12.13'), '2001:db8:1234:4567::10.11.12.13') raises(ValueError, convert, '1hostnamewithleadingnumeric') raises(ValueError, convert, '255.255') raises(ValueError, convert, '12345678') raises(ValueError, convert, '999.999.999.999') raises(ValueError, convert, 'a!badhostname') raises(ValueError, convert, '2001:DB8:0123:4567:89AB:cdef:0:1:2') raises(ValueError, convert, '2001:DB8:0123:4567::10.11.12.13.14') def test_existing_directory(self): convert = self.types.get('existing-directory') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(os.path.dirname(here)), os.path.dirname(here)) raises(ValueError, convert, tempfile.mktemp()) def test_existing_file(self): convert = self.types.get('existing-file') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) raises(ValueError, convert, tempfile.mktemp()) def test_existing_path(self): convert = self.types.get('existing-path') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) eq(convert(os.path.dirname(here)), os.path.dirname(here)) raises(ValueError, convert, tempfile.mktemp()) def test_existing_dirpath(self): convert = self.types.get('existing-dirpath') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) raises(ValueError, convert, '/a/hopefully/nonexistent/path') raises(ValueError, convert, here + '/bogus') def test_byte_size(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('byte-size') eq(convert('128'), 128) eq(convert('128KB'), 128*1024) eq(convert('128MB'), 128*1024*1024) eq(convert('128GB'), 128*1024*1024*1024) raises(ValueError, convert, '128TB') eq(convert('128'), 128) eq(convert('128kb'), 128*1024) eq(convert('128mb'), 128*1024*1024) eq(convert('128gb'), 128*1024*1024*1024) raises(ValueError, convert, '128tb') def test_time_interval(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('time-interval') eq(convert('120'), 120) eq(convert('120S'), 120) eq(convert('120M'), 120*60) eq(convert('120H'), 120*60*60) eq(convert('120D'), 120*60*60*24) raises(ValueError, convert, '120W') eq(convert('120'), 120) eq(convert('120s'), 120) eq(convert('120m'), 120*60) eq(convert('120h'), 120*60*60) eq(convert('120d'), 120*60*60*24) raises(ValueError, convert, '120w') def test_timedelta(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('timedelta') eq(convert('4w'), datetime.timedelta(weeks=4)) eq(convert('2d'), datetime.timedelta(days=2)) eq(convert('7h'), datetime.timedelta(hours=7)) eq(convert('12m'), datetime.timedelta(minutes=12)) eq(convert('14s'), datetime.timedelta(seconds=14)) eq(convert('4w 2d 7h 12m 14s'), datetime.timedelta(2, 14, minutes=12, hours=7, weeks=4)) class RegistryTestCase(unittest.TestCase): def test_registry_does_not_mask_toplevel_imports(self): old_sys_path = sys.path[:] tmpdir = tempfile.mkdtemp(prefix="test_datatypes_") fn = os.path.join(tmpdir, "datatypes.py") f = open(fn, "w") f.write(TEST_DATATYPE_SOURCE) f.close() registry = ZConfig.datatypes.Registry() # we really want the temp area to override everything else: sys.path.insert(0, tmpdir) try: datatype = registry.get("datatypes.my_sample_datatype") finally: shutil.rmtree(tmpdir) sys.path[:] = old_sys_path self.assertEqual(datatype, 42) TEST_DATATYPE_SOURCE = """ # sample datatypes file my_sample_datatype = 42 """ def test_suite(): suite = unittest.makeSuite(DatatypeTestCase) suite.addTest(unittest.makeSuite(RegistryTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') ZConfig-3.1.0/ZConfig/tests/test_loader.py0000644000076500000240000003310612610530667020561 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of ZConfig.loader classes and helper functions.""" import os.path import sys import tempfile import unittest import ZConfig import ZConfig.loader import ZConfig.url from ZConfig.tests.support import CONFIG_BASE, TestHelper try: import urllib2 except ImportError: # Python 3 support. import urllib.request as urllib2 try: from StringIO import StringIO except ImportError: # Python 3 support. from io import StringIO try: myfile = __file__ except NameError: myfile = sys.argv[0] myfile = os.path.abspath(myfile) LIBRARY_DIR = os.path.join(os.path.dirname(myfile), "library") class LoaderTestCase(TestHelper, unittest.TestCase): def test_schema_caching(self): loader = ZConfig.loader.SchemaLoader() url = ZConfig.url.urljoin(CONFIG_BASE, "simple.xml") schema1 = loader.loadURL(url) schema2 = loader.loadURL(url) self.assertTrue(schema1 is schema2) def test_simple_import_with_cache(self): loader = ZConfig.loader.SchemaLoader() url1 = ZConfig.url.urljoin(CONFIG_BASE, "library.xml") schema1 = loader.loadURL(url1) sio = StringIO("" " " "
" "") url2 = ZConfig.url.urljoin(CONFIG_BASE, "stringio") schema2 = loader.loadFile(sio, url2) self.assertTrue(schema1.gettype("type-a") is schema2.gettype("type-a")) def test_simple_import_using_prefix(self): self.load_schema_text("""\ """) def test_import_errors(self): # must specify exactly one of package or src self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("")) self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("" " " "")) # cannot specify src and file self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("" " " "")) # cannot specify module as package sio = StringIO("" " " "") try: ZConfig.loadSchemaFile(sio) except ZConfig.SchemaResourceError as e: self.assertEqual(e.filename, "component.xml") self.assertEqual(e.package, "ZConfig.tests.test_loader") self.assertTrue(e.path is None) # make sure the str() doesn't raise an unexpected exception str(e) else: self.fail("expected SchemaResourceError") def test_import_from_package(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assertTrue(schema.gettype("widget-a") is not None) def test_import_from_package_with_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assertTrue(schema.gettype("extra-type") is not None) def test_import_from_package_extra_directory(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assertTrue(schema.gettype("extra-thing") is not None) def test_import_from_package_with_missing_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") try: loader.loadFile(sio) except ZConfig.SchemaResourceError as e: self.assertEqual(e.filename, "notthere.xml") self.assertEqual(e.package, "ZConfig.tests.library.widget") self.assertTrue(e.path) # make sure the str() doesn't raise an unexpected exception str(e) else: self.fail("expected SchemaResourceError") def test_import_from_package_with_directory_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") self.assertRaises(ZConfig.SchemaError, loader.loadFile, sio) def test_import_two_components_one_package(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " " " "") schema = loader.loadFile(sio) schema.gettype("widget-a") schema.gettype("extra-type") def test_import_component_twice_1(self): # Make sure we can import a component twice from a schema. # This is most likely to occur when the component is imported # from each of two other components, or from the top-level # schema and a component. loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " " " "") schema = loader.loadFile(sio) schema.gettype("widget-a") def test_import_component_twice_2(self): # Make sure we can import a component from a config file even # if it has already been imported from the schema. loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) loader = ZConfig.loader.ConfigLoader(schema) sio = StringIO("%import ZConfig.tests.library.widget") loader.loadFile(sio) def test_urlsplit_urlunsplit(self): # Extracted from Python's test.test_urlparse module: for url, parsed, split in [ ('http://www.python.org', ('http', 'www.python.org', '', '', '', ''), ('http', 'www.python.org', '', '', '')), ('http://www.python.org#abc', ('http', 'www.python.org', '', '', '', 'abc'), ('http', 'www.python.org', '', '', 'abc')), ('http://www.python.org/#abc', ('http', 'www.python.org', '/', '', '', 'abc'), ('http', 'www.python.org', '/', '', 'abc')), ("http://a/b/c/d;p?q#f", ('http', 'a', '/b/c/d', 'p', 'q', 'f'), ('http', 'a', '/b/c/d;p', 'q', 'f')), ('file:///tmp/junk.txt', ('file', '', '/tmp/junk.txt', '', '', ''), ('file', '', '/tmp/junk.txt', '', '')), ]: result = ZConfig.url.urlsplit(url) self.assertEqual(result, split) result2 = ZConfig.url.urlunsplit(result) self.assertEqual(result2, url) def test_file_url_normalization(self): self.assertEqual( ZConfig.url.urlnormalize("file:/abc/def"), "file:///abc/def") self.assertEqual( ZConfig.url.urlunsplit(("file", "", "/abc/def", "", "")), "file:///abc/def") self.assertEqual( ZConfig.url.urljoin("file:/abc/", "def"), "file:///abc/def") self.assertEqual( ZConfig.url.urldefrag("file:/abc/def#frag"), ("file:///abc/def", "frag")) def test_isPath(self): assertTrue = self.assertTrue isPath = ZConfig.loader.BaseLoader().isPath assertTrue(isPath("abc")) assertTrue(isPath("abc/def")) assertTrue(isPath("/abc")) assertTrue(isPath("/abc/def")) assertTrue(isPath(r"\abc")) assertTrue(isPath(r"\abc\def")) assertTrue(isPath(r"c:\abc\def")) assertTrue(isPath("/ab:cd")) assertTrue(isPath(r"\ab:cd")) assertTrue(isPath("long name with spaces")) assertTrue(isPath("long name:with spaces")) assertTrue(not isPath("ab:cd")) assertTrue(not isPath("http://www.example.com/")) assertTrue(not isPath("http://www.example.com/sample.conf")) assertTrue(not isPath("file:///etc/zope/zope.conf")) assertTrue(not isPath("file:///c|/foo/bar.conf")) class TestNonExistentResources(unittest.TestCase): # XXX Not sure if this is the best approach for these. These # tests make sure that the error reported by ZConfig for missing # resources is handled in a consistent way. Since ZConfig uses # urllib2.urlopen() for opening all resources, what we do is # replace that function with one that always raises an exception. # Since urllib2.urlopen() can raise either IOError or OSError # (depending on the version of Python), we run test for each # exception. urllib2.urlopen() is restored after running the # test. def setUp(self): self.urllib2_urlopen = urllib2.urlopen urllib2.urlopen = self.fake_urlopen def tearDown(self): urllib2.urlopen = self.urllib2_urlopen def fake_urlopen(self, url): raise self.error() def test_nonexistent_file_ioerror(self): self.error = IOError self.check_nonexistent_file() def test_nonexistent_file_oserror(self): self.error = OSError self.check_nonexistent_file() def check_nonexistent_file(self): fn = tempfile.mktemp() schema = ZConfig.loadSchemaFile(StringIO("")) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadSchema, fn) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfig, schema, fn) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfigFile, schema, StringIO("%include " + fn)) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadSchema, "http://www.zope.org/no-such-document/") self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfig, schema, "http://www.zope.org/no-such-document/") class TestResourcesInZip(unittest.TestCase): def setUp(self): self.old_path = sys.path[:] # now add our sample EGG to sys.path: zipfile = os.path.join(os.path.dirname(myfile), "foosample.zip") sys.path.append(zipfile) def tearDown(self): sys.path[:] = self.old_path def test_zip_import_component_from_schema(self): sio = StringIO('''
''') schema = ZConfig.loadSchemaFile(sio) t = schema.gettype("sample") self.assertFalse(t.isabstract()) def test_zip_import_component_from_config(self): sio = StringIO('''
''') schema = ZConfig.loadSchemaFile(sio) sio = StringIO(''' %import foo.sample data value ''') config, _ = ZConfig.loadConfigFile(schema, sio) self.assertEqual(config.something.data, "| value |") def test_suite(): suite = unittest.makeSuite(LoaderTestCase) suite.addTest(unittest.makeSuite(TestNonExistentResources)) suite.addTest(unittest.makeSuite(TestResourcesInZip)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') ZConfig-3.1.0/ZConfig/tests/test_readme.py0000644000076500000240000000217512610530667020552 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import logging options = doctest.REPORT_NDIFF | doctest.ELLIPSIS old = {} def setUp(test): global old logger = logging.getLogger() old['level'] = logger.level old['handlers'] = logger.handlers[:] def tearDown(test): logger = logging.getLogger() logger.level = old['level'] logger.handlers = old['handlers'] def test_suite(): return doctest.DocFileSuite( '../../README.txt', optionflags=options, setUp=setUp, tearDown=tearDown, ) ZConfig-3.1.0/ZConfig/tests/test_schema.py0000644000076500000240000012343712610530667020562 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of ZConfig schemas.""" import unittest import ZConfig from ZConfig.tests.support import TestHelper, CONFIG_BASE def uppercase(value): return str(value).upper() def appsection(value): return MySection(value) def get_foo(section): return section.foo class MySection: def __init__(self, value): self.conf = value def get_section_attributes(section): L = list(section.getSectionAttributes()) return sorted(L) class SchemaTestCase(TestHelper, unittest.TestCase): """Tests of the basic schema support itself.""" def test_minimal_schema(self): schema = self.load_schema_text("") self.assertEqual(len(schema), 0) self.assertRaises(IndexError, lambda schema=schema: schema[0]) self.assertRaises(ZConfig.ConfigurationError, schema.getinfo, "foo") def test_simple(self): schema, conf = self.load_both("simple.xml", "simple.conf") self._verifySimpleConf(conf) def _verifySimpleConf(self,conf): eq = self.assertEqual eq(conf.var1, 'abc') eq(conf.int_var, 12) eq(conf.float_var, 12.02) eq(conf.neg_int, -2) check = self.assertTrue check(conf.true_var_1) check(conf.true_var_2) check(conf.true_var_3) check(not conf.false_var_1) check(not conf.false_var_2) check(not conf.false_var_3) def test_app_datatype(self): dtname = __name__ + ".uppercase" schema = self.load_schema_text("""\ abc abc not lower case """ % (dtname, dtname, dtname, dtname)) conf = self.load_config_text(schema, """\ a qwerty c upp c er c case """) eq = self.assertEqual eq(conf.a, 'QWERTY') eq(conf.b, 'ABC') eq(conf.c, ['UPP', 'ER', 'CASE']) eq(conf.d, ['NOT', 'LOWER', 'CASE']) eq(get_section_attributes(conf), ["a", "b", "c", "d"]) def test_app_sectiontype(self): schema = self.load_schema_text("""\
""" % __name__) conf = self.load_config_text(schema, """\ sample 42 """) self.assertTrue(isinstance(conf, MySection)) o1 = conf.conf.sect self.assertTrue(isinstance(o1, MySection)) self.assertEqual(o1.conf.sample, 42) def test_empty_sections(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\
""") self.assertTrue(conf.s1 is not None) self.assertTrue(conf.s2 is not None) self.assertEqual(get_section_attributes(conf), ["s1", "s2"]) def test_deeply_nested_sections(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ key sect3-value key sect2-value """) eq = self.assertEqual eq(conf.sect.sect.sect.key, "type1-value") eq(conf.sect.sect.key, "sect2-value") eq(conf.sect.key, "sect3-value") eq(get_section_attributes(conf), ["sect"]) eq(get_section_attributes(conf.sect), ["key", "sect"]) eq(get_section_attributes(conf.sect.sect), ["key", "sect"]) eq(get_section_attributes(conf.sect.sect.sect), ["key"]) def test_multivalued_keys(self): schema = self.load_schema_text("""\ 1 2 3 4 5 """) conf = self.load_config_text(schema, """\ a foo a bar c 41 c 42 c 43 """, num_handlers=2) L = [] self.handlers({'abc': L.append, 'DEF': L.append}) self.assertEqual(L, [['foo', 'bar'], conf]) L = [] self.handlers({'abc': None, 'DEF': L.append}) self.assertEqual(L, [conf]) self.assertEqual(conf.a, ['foo', 'bar']) self.assertEqual(conf.b, [1, 2]) self.assertEqual(conf.c, [41, 42, 43]) self.assertEqual(conf.d, []) self.assertEqual(get_section_attributes(conf), ["a", "b", "c", "d"]) def test_multikey_required(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_multisection_required(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_key_required_but_missing(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_section_required_but_missing(self): schema = self.load_schema_text("""\
""") self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_key_default_element(self): self.assertRaises( ZConfig.SchemaError, self.load_schema_text, """\ text """) def test_bad_handler_maps(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ a foo b bar """, num_handlers=2) self.assertEqual(get_section_attributes(conf), ["a", "b"]) self.assertRaises(ZConfig.ConfigurationError, self.handlers, {'abc': id, 'ABC': id, 'def': id}) self.assertRaises(ZConfig.ConfigurationError, self.handlers, {}) def test_handler_ordering(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """, num_handlers=3) L = [] self.handlers({'a': L.append, 'b': L.append, 'c': L.append}) outer = conf.sect_outer inner = outer.sect_inner self.assertEqual(L, [inner, outer, conf]) def test_duplicate_section_names(self): schema = self.load_schema_text("""\
""") self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, """\ """) conf = self.load_config_text(schema, """\ """) def test_disallowed_duplicate_attribute(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_unknown_datatype_name(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, "") def test_load_abstracttype(self): schema = self.load_schema_text("""\ This is an abstract section type. """) # check the types that get defined t = schema.gettype("group") self.assertTrue(t.isabstract()) t1 = schema.gettype("t1") self.assertTrue(not t1.isabstract()) self.assertTrue(t.getsubtype("t1") is t1) t2 = schema.gettype("t2") self.assertTrue(not t2.isabstract()) self.assertTrue(t.getsubtype("t2") is t2) self.assertRaises(ZConfig.ConfigurationError, t.getsubtype, "group") self.assertTrue(t1 is not t2) # try loading a config that relies on this schema conf = self.load_config_text(schema, """\ k1 value1 k2 value2 """) eq = self.assertEqual eq(get_section_attributes(conf), ["g"]) eq(len(conf.g), 4) eq(conf.g[0].k1, "default1") eq(conf.g[1].k1, "value1") eq(conf.g[2].k2, "default2") eq(conf.g[3].k2, "value2") # white box: self.assertTrue(conf.g[0].getSectionDefinition() is t1) self.assertTrue(conf.g[1].getSectionDefinition() is t1) self.assertTrue(conf.g[2].getSectionDefinition() is t2) self.assertTrue(conf.g[3].getSectionDefinition() is t2) def test_abstracttype_extension(self): schema = self.load_schema_text("""\
""") abstype = schema.gettype("group") self.assertTrue(schema.gettype("extra") is abstype.getsubtype("extra")) # make sure we can use the extension in a config: conf = self.load_config_text(schema, "") self.assertEqual(conf.thing.getSectionType(), "extra") self.assertEqual(get_section_attributes(conf), ["thing"]) self.assertEqual(get_section_attributes(conf.thing), []) def test_abstracttype_extension_errors(self): # specifying a non-existant abstracttype self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # specifying something that isn't an abstracttype self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_arbitrary_key(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "some-key 42") self.assertEqual(conf.keymap, {'some-key': 42}) self.assertEqual(get_section_attributes(conf), ["keymap"]) def test_arbitrary_multikey_required(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ some-key 42 some-key 43 """) self.assertEqual(conf.keymap, {'some-key': [42, 43]}) def test_arbitrary_multikey_optional(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ some-key 42 some-key 43 """) self.assertEqual(conf.stuff.keymap, {'some-key': ['42', '43']}) self.assertEqual(get_section_attributes(conf), ["stuff"]) def test_arbitrary_multikey_optional_empty(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.stuff.keymap, {}) def test_arbitrary_multikey_with_defaults(self): schema = self.load_schema_text("""\ value-a1 value-a2 value-b """) conf = self.load_config_text(schema, "") self.assertEqual(conf.keymap, {'a': ['value-a1', 'value-a2'], 'b': ['value-b']}) def test_arbitrary_multikey_with_unkeyed_default(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ value-a1 """) def test_arbitrary_key_with_defaults(self): schema = self.load_schema_text("""\ value-a value-b """) conf = self.load_config_text(schema, "") self.assertEqual(conf.keymap, {'a': 'value-a', 'b': 'value-b'}) def test_arbitrary_key_with_unkeyed_default(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ value-a1 """) def test_arbitrary_keys_with_others(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ some-key 42 k2 3 """) self.assertEqual(conf.k1, 'v1') self.assertEqual(conf.k2, 3) self.assertEqual(conf.keymap, {'some-key': 42}) self.assertEqual(get_section_attributes(conf), ["k1", "k2", "keymap"]) def test_arbitrary_key_missing(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "# empty config file") def test_arbitrary_key_bad_schema(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_getrequiredtypes(self): schema = self.load_schema("library.xml") self.assertEqual(schema.getrequiredtypes(), []) schema = self.load_schema_text("""\
""") L = sorted(schema.getrequiredtypes()) self.assertEqual(L, ["used"]) def test_getunusedtypes(self): schema = self.load_schema("library.xml") L = sorted(schema.getunusedtypes()) self.assertEqual(L, ["type-a", "type-b"]) schema = self.load_schema_text("""\
""") self.assertEqual(schema.getunusedtypes(), ["unused"]) def test_section_value_mutation(self): schema, conf = self.load_both("simple.xml", "simple.conf") orig = conf.empty new = [] conf.empty = new self.assertTrue(conf.empty is new) def test_simple_anonymous_section(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") def test_simple_anonymous_section_without_name(self): # make sure we get the same behavior without name='*' schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") def test_simple_anynamed_section(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") self.assertEqual(conf.attr.getSectionName(), "name") # if we omit the name, it's an error self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_nested_abstract_sectiontype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """) def test_nested_abstract_sectiontype_without_name(self): # make sure we get the same behavior without name='*' schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """) def test_reserved_attribute_prefix(self): template = """\ %s """ def check(thing, self=self, template=template): text = template % thing self.assertRaises(ZConfig.SchemaError, self.load_schema_text, text) check("") check("") check("") check("") check("
") check("
") check("") check("") def test_sectiontype_as_schema(self): schema = self.load_schema_text("""\
""") t = schema.gettype("t") conf = self.load_config_text(t, "") self.assertEqual(conf.tkey, "tkey-default") self.assertEqual(conf.section.skey, "skey-default") self.assertEqual(get_section_attributes(conf), ["section", "tkey"]) self.assertEqual(get_section_attributes(conf.section), ["skey"]) def test_datatype_conversion_error(self): schema_url = "file:///tmp/fake-url-1.xml" config_url = "file:///tmp/fake-url-2.xml" schema = self.load_schema_text("""\ """, url=schema_url) e = self.get_data_conversion_error( schema, "", config_url) self.assertEqual(e.url, schema_url) self.assertEqual(e.lineno, 2) e = self.get_data_conversion_error(schema, """\ # comment key splat """, config_url) self.assertEqual(e.url, config_url) self.assertEqual(e.lineno, 3) def get_data_conversion_error(self, schema, src, url): try: self.load_config_text(schema, src, url=url) except ZConfig.DataConversionError as e: return e else: self.fail("expected ZConfig.DataConversionError") def test_numeric_section_name(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "") self.assertEqual(len(conf.things), 1) def test_sectiontype_extension(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ k1 k1-value k2 k2-value """) eq = self.assertEqual eq(conf.s.k1, "k1-value") eq(conf.s.k2, "k2-value") eq(get_section_attributes(conf), ["s"]) eq(get_section_attributes(conf.s), ["k1", "k2"]) def test_sectiontype_extension_errors(self): # cannot override key from base self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # cannot extend non-existing section self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # cannot extend abstract type self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_sectiontype_derived_keytype(self): # make sure that a derived section type inherits the keytype # of its base schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ foo bar Foo BAR """) self.assertEqual(conf.foo.foo, "bar") self.assertEqual(conf.foo.Foo, "BAR") self.assertEqual(get_section_attributes(conf.foo), ["Foo", "foo"]) def test_sectiontype_override_keytype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ ident1 foo Ident2 bar EXAMPLE.COM foo """) L = sorted(conf.base.map.items()) self.assertEqual(L, [("Ident2", "bar"), ("ident1", "foo")]) L = sorted(conf.derived.map.items()) self.assertEqual(L, [("example.com", "foo")]) self.assertEqual(get_section_attributes(conf), ["base", "derived"]) def test_keytype_applies_to_default_key(self): schema = self.load_schema_text("""\ 42 24
""") conf = self.load_config_text(schema, "") items = sorted(conf.sect.mapping.items()) self.assertEqual(items, [("bar", "24"), ("foo", "42")]) def test_duplicate_default_key_checked_in_schema(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ 42 24
""") def test_default_keys_rechecked_clash_in_derived_sectiontype(self): # If the default values associated with a can't # be supported by a new keytype for a derived sectiontype, an # error should be indicated. self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ 42 42
""") def test_default_keys_rechecked_dont_clash_in_derived_sectiontype(self): # If the default values associated with a can't # be supported by a new keytype for a derived sectiontype, an # error should be indicated. schema = self.load_schema_text("""\ 42 42
""") conf = self.load_config_text(schema, """\ """) base = sorted(conf.base.mapping.items()) self.assertEqual(base, [("Foo", ["42"]), ("foo", ["42"])]) sect = sorted(conf.sect.mapping.items()) self.assertEqual(sect, [("foo", ["42", "42"])]) def test_sectiontype_inherited_datatype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ foo bar """) self.assertEqual(conf.splat, "bar") def test_schema_keytype(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "host.example.com 127.0.0.1\n" "www.example.org 127.0.0.2\n") table = conf.table self.assertEqual(len(table), 2) L = sorted(table.items()) self.assertEqual(L, [("host.example.com", "127.0.0.1"), ("www.example.org", "127.0.0.2")]) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "abc. 127.0.0.1") def test_keytype_identifier(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "Foo Foo-value\n" "foo foo-value\n") self.assertEqual(conf.foo, "foo-value") self.assertEqual(conf.Foo, "Foo-value") self.assertEqual(get_section_attributes(conf), ["Foo", "foo"]) # key mis-match based on case: self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "FOO frob\n") # attribute names conflict, since the keytype isn't used to # generate attribute names self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_datatype_casesensitivity(self): self.load_schema_text("") def test_simple_extends(self): schema = self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) self._verifySimpleConf(self.load_config(schema, "simple.conf")) def test_extends_fragment_failure(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, "" % CONFIG_BASE) def test_extends_description_override(self): schema = self.load_schema_text("""\ overriding description
""" % (CONFIG_BASE, CONFIG_BASE)) description = schema.description.strip() self.assertEqual(description, "overriding description") def test_extends_description_first_extended_wins(self): schema = self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) description = schema.description.strip() self.assertEqual(description, "base description") def test_multi_extends_implicit_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_explicit_datatype_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_explicit_keytype_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE, __name__)) def test_multi_extends_datatype_conflict(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """ % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_keytype_conflict(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """ % (CONFIG_BASE, CONFIG_BASE)) def test_multiple_descriptions_is_error(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ foo bar """) def test_srepr(self): from ZConfig.schema import _srepr try: FOO = unicode('foo') except NameError: FOO = 'foo' self.assertEqual(_srepr('foo'), "'foo'") self.assertEqual(_srepr(FOO), "'foo'") def test_suite(): return unittest.makeSuite(SchemaTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') ZConfig-3.1.0/ZConfig/tests/test_schemaless.py0000644000076500000240000000146612610530667021446 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """\ Test driver for ZConfig.schemaless. """ __docformat__ = "reStructuredText" import doctest def test_suite(): return doctest.DocFileSuite("schemaless.txt", package="ZConfig") ZConfig-3.1.0/ZConfig/tests/test_subst.py0000644000076500000240000000730312610530667020453 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the string interpolation module.""" # This is needed to support Python 2.1. from __future__ import nested_scopes import os import unittest from ZConfig import SubstitutionReplacementError, SubstitutionSyntaxError from ZConfig.substitution import isname, substitute class SubstitutionTestCase(unittest.TestCase): def test_simple_names(self): d = {"name": "value", "name1": "abc", "name_": "def", "_123": "ghi"} def check(s, v): self.assertEqual(substitute(s, d), v) check("$name", "value") check(" $name ", " value ") check("${name}", "value") check(" ${name} ", " value ") check("$name$name", "valuevalue") check("$name1$name", "abcvalue") check("$name_$name", "defvalue") check("$_123$name", "ghivalue") check("$name $name", "value value") check("$name1 $name", "abc value") check("$name_ $name", "def value") check("$_123 $name", "ghi value") check("splat", "splat") check("$$", "$") check("$$$name$$", "$value$") # Check for an ENV var self.assertEqual(substitute("$(PATH)", d), os.getenv("PATH")) def test_undefined_names(self): d = {"name": "value"} self.assertRaises(SubstitutionReplacementError, substitute, "$splat", d) self.assertRaises(SubstitutionReplacementError, substitute, "$splat1", d) self.assertRaises(SubstitutionReplacementError, substitute, "$splat_", d) # An undefined ENV should also rise self.assertRaises(SubstitutionReplacementError, substitute, "$(MY_SUPER_PATH)", d) def test_syntax_errors(self): d = {"name": "${next"} def check(s): self.assertRaises(SubstitutionSyntaxError, substitute, s, d) check("${") check("${name") check("${1name}") check("${ name}") check("$(") check("$(name") check("$(1name)") check("$( name)") def test_edge_cases(self): # It's debatable what should happen for these cases, so we'll # follow the lead of the Bourne shell here. def check(s): self.assertRaises(SubstitutionSyntaxError, substitute, s, {}) check("$1") check("$") check("$ stuff") def test_non_nesting(self): d = {"name": "$value"} self.assertEqual(substitute("$name", d), "$value") def test_isname(self): self.assertTrue(isname("abc")) self.assertTrue(isname("abc_def")) self.assertTrue(isname("_abc")) self.assertTrue(isname("abc_")) self.assertTrue(not isname("abc-def")) self.assertTrue(not isname("-def")) self.assertTrue(not isname("abc-")) self.assertTrue(not isname("")) def test_suite(): return unittest.makeSuite(SubstitutionTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') ZConfig-3.1.0/ZConfig/tests/zipsource/0000755000076500000240000000000012610530670017714 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/zipsource/foo/0000755000076500000240000000000012610530670020477 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/zipsource/foo/__init__.py0000644000076500000240000000004612610530667022616 0ustar do3ccstaff00000000000000# This directory is a Python package. ZConfig-3.1.0/ZConfig/tests/zipsource/foo/sample/0000755000076500000240000000000012610530670021760 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig/tests/zipsource/foo/sample/__init__.py0000644000076500000240000000004612610530667024077 0ustar do3ccstaff00000000000000# This directory is a Python package. ZConfig-3.1.0/ZConfig/tests/zipsource/foo/sample/component.xml0000644000076500000240000000035212610530667024512 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig/tests/zipsource/foo/sample/datatypes.py0000644000076500000240000000017412610530667024340 0ustar do3ccstaff00000000000000"""Sample datatypes used for testing. """ __docformat__ = "reStructuredText" def data(value): return "| %s |" % value ZConfig-3.1.0/ZConfig/tests/zipsource/README.txt0000644000076500000240000000015412610530667021420 0ustar do3ccstaff00000000000000This directory contains a sample package that is used to create the 'foosample.zip' file used in the tests. ZConfig-3.1.0/ZConfig/url.py0000644000076500000240000000325612610530667015717 0ustar do3ccstaff00000000000000############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """urlparse-like helpers that normalize file: URLs. ZConfig and urllib2 expect file: URLs to consistently use the '//' hostpart seperator; the functions here enforce this constraint. """ try: import urlparse as _urlparse except ImportError: # Python 3 support import urllib.parse as _urlparse urlsplit = _urlparse.urlsplit def urlnormalize(url): lc = url.lower() if lc.startswith("file:/") and not lc.startswith("file:///"): url = "file://" + url[5:] return url def urlunsplit(parts): parts = list(parts) parts.insert(3, '') url = _urlparse.urlunparse(tuple(parts)) if (parts[0] == "file" and url.startswith("file:/") and not url.startswith("file:///")): url = "file://" + url[5:] return url def urldefrag(url): url, fragment = _urlparse.urldefrag(url) return urlnormalize(url), fragment def urljoin(base, relurl): url = _urlparse.urljoin(base, relurl) if url.startswith("file:/") and not url.startswith("file:///"): url = "file://" + url[5:] return url ZConfig-3.1.0/ZConfig.egg-info/0000755000076500000240000000000012610530670016221 5ustar do3ccstaff00000000000000ZConfig-3.1.0/ZConfig.egg-info/dependency_links.txt0000644000076500000240000000000112610530670022267 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig.egg-info/not-zip-safe0000644000076500000240000000000112610530670020447 0ustar do3ccstaff00000000000000 ZConfig-3.1.0/ZConfig.egg-info/PKG-INFO0000644000076500000240000003450212610530670017322 0ustar do3ccstaff00000000000000Metadata-Version: 1.1 Name: ZConfig Version: 3.1.0 Summary: Structured Configuration Library Home-page: http://www.zope.org/Members/fdrake/zconfig/ Author: Zope Foundation and Contributors Author-email: fred@zope.com License: ZPL 2.1 Description: ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ========================== Change History for ZConfig ========================== 3.1.0 (2015-10-17) ------------------ - Add ability to do variable substitution from environment variables using $() syntax. 3.0.4 (2014-03-20) ------------------ - Added Python 3.4 support. 3.0.3 (2013-03-02) ------------------ - Added Python 3.2 support. 3.0.2 (2013-02-14) ------------------ - Fixed ResourceWarning in BaseLoader.openResource(). 3.0.1 (2013-02-13) ------------------ - Removed an accidentally left `pdb` statement from the code. - Fix a bug in Python 3 with the custom string `repr()` function. 3.0.0 (2013-02-13) ------------------ - Added Python 3.3 support. - Dropped Python 2.4 and 2.5 support. 2.9.3 (2012-06-25) ------------------ - Fixed: port values of 0 weren't allowed. Port 0 is used to request an ephemeral port. 2.9.2 (2012-02-11) ------------------ - Adjust test classes to avoid base classes being considered separate test cases by (at least) the "nose" test runner. 2.9.1 (2012-02-11) ------------------ - Make FileHandler.reopen thread safe. 2.9.0 (2011-03-22) ------------------ - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. 2.8.0 (2010-04-13) ------------------ - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. 2.7.1 (2009-06-13) ------------------ - Improved documentation - Fixed tests failures on windows. 2.7.0 (2009-06-11) ------------------ - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. 2.6.1 (2008-12-05) ------------------ - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. 2.6.0 (2008-09-03) ------------------ - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. 2.5.1 (2007-12-24) ------------------ - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. 2.3.1 (2005-08-21) ------------------ - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. 2.3 (2005-05-18) ---------------- - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. 2.2 (2004-04-21) ---------------- - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. 2.1 (2004-04-12) ---------------- - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. 2.0 (2003-10-27) ---------------- - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. 1.0 (2003-03-25) ---------------- - Initial release. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development ZConfig-3.1.0/ZConfig.egg-info/SOURCES.txt0000644000076500000240000000550612610530670020113 0ustar do3ccstaff00000000000000CHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.cfg setup.py tox.ini ZConfig/__init__.py ZConfig/cfgparser.py ZConfig/cmdline.py ZConfig/datatypes.py ZConfig/info.py ZConfig/loader.py ZConfig/matcher.py ZConfig/schema.py ZConfig/schemaless.py ZConfig/schemaless.txt ZConfig/substitution.py ZConfig/url.py ZConfig.egg-info/PKG-INFO ZConfig.egg-info/SOURCES.txt ZConfig.egg-info/dependency_links.txt ZConfig.egg-info/not-zip-safe ZConfig.egg-info/top_level.txt ZConfig/components/__init__.py ZConfig/components/basic/__init__.py ZConfig/components/basic/component.xml ZConfig/components/basic/mapping.py ZConfig/components/basic/mapping.xml ZConfig/components/basic/tests/__init__.py ZConfig/components/basic/tests/test_mapping.py ZConfig/components/logger/__init__.py ZConfig/components/logger/abstract.xml ZConfig/components/logger/base-logger.xml ZConfig/components/logger/component.xml ZConfig/components/logger/datatypes.py ZConfig/components/logger/eventlog.xml ZConfig/components/logger/factory.py ZConfig/components/logger/handlers.py ZConfig/components/logger/handlers.xml ZConfig/components/logger/logger.py ZConfig/components/logger/logger.xml ZConfig/components/logger/loghandler.py ZConfig/components/logger/tests/__init__.py ZConfig/components/logger/tests/test_logger.py ZConfig/tests/__init__.py ZConfig/tests/foosample.zip ZConfig/tests/support.py ZConfig/tests/test_cfgimports.py ZConfig/tests/test_cmdline.py ZConfig/tests/test_config.py ZConfig/tests/test_cookbook.py ZConfig/tests/test_datatypes.py ZConfig/tests/test_loader.py ZConfig/tests/test_readme.py ZConfig/tests/test_schema.py ZConfig/tests/test_schemaless.py ZConfig/tests/test_subst.py ZConfig/tests/input/base-datatype1.xml ZConfig/tests/input/base-datatype2.xml ZConfig/tests/input/base-keytype1.xml ZConfig/tests/input/base-keytype2.xml ZConfig/tests/input/base.xml ZConfig/tests/input/include.conf ZConfig/tests/input/inner.conf ZConfig/tests/input/library.xml ZConfig/tests/input/logger.xml ZConfig/tests/input/outer.conf ZConfig/tests/input/simple.conf ZConfig/tests/input/simple.xml ZConfig/tests/input/simplesections.conf ZConfig/tests/input/simplesections.xml ZConfig/tests/library/README.txt ZConfig/tests/library/__init__.py ZConfig/tests/library/thing/__init__.py ZConfig/tests/library/thing/component.xml ZConfig/tests/library/thing/extras/extras.xml ZConfig/tests/library/widget/__init__.py ZConfig/tests/library/widget/component.xml ZConfig/tests/library/widget/extra.xml ZConfig/tests/zipsource/README.txt ZConfig/tests/zipsource/foo/__init__.py ZConfig/tests/zipsource/foo/sample/__init__.py ZConfig/tests/zipsource/foo/sample/component.xml ZConfig/tests/zipsource/foo/sample/datatypes.py doc/Makefile doc/README.txt doc/schema.dtd doc/xmlmarkup.perl doc/xmlmarkup.sty doc/zconfig.pdf doc/zconfig.tex scripts/zconfig scripts/zconfig_schema2htmlZConfig-3.1.0/ZConfig.egg-info/top_level.txt0000644000076500000240000000001012610530670020742 0ustar do3ccstaff00000000000000ZConfig