././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.7042024 tap.py-3.1/0000755000076500000240000000000000000000000011421 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1612453674.0 tap.py-3.1/AUTHORS0000644000076500000240000000051200000000000012467 0ustar00mattstafftappy was originally created by Matt Layman. Contributors ------------ * Adeodato Simó * Andrew McNamara * Chris Clarke * Erik Cederstrand * Marc Abramowitz * Mark E. Hamilton * Matt Layman * meejah (https://meejah.ca) * Michael F. Lamb (http://datagrok.org) * Nicolas Caniart * Richard Bosworth * Ross Burton * Simon McVittie ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1568681898.0 tap.py-3.1/LICENSE0000644000076500000240000000247600000000000012437 0ustar00mattstaffCopyright (c) 2019, Matt Layman and contributors. See AUTHORS for more details. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640755752.0 tap.py-3.1/MANIFEST.in0000644000076500000240000000013500000000000013156 0ustar00mattstaffinclude AUTHORS include LICENSE include README.md recursive-include docs * prune docs/_build ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1640756615.704442 tap.py-3.1/PKG-INFO0000644000076500000240000001631600000000000012525 0ustar00mattstaffMetadata-Version: 2.1 Name: tap.py Version: 3.1 Summary: Test Anything Protocol (TAP) tools Home-page: https://github.com/python-tap/tappy Author: Matt Layman Author-email: matthewlayman@gmail.com License: BSD Keywords: TAP,unittest Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Testing Provides-Extra: yaml License-File: LICENSE tappy is a set of tools for working with the `Test Anything Protocol (TAP) `_, a line based test protocol for recording test data in a standard way. Follow tappy development on `GitHub `_. Developer documentation is on `Read the Docs `_. Releases ======== Version 3.1, Released December 29, 2021 --------------------------------------- * Add support for Python 3.10. * Add support for Python 3.9. * Add support for Python 3.8. * Drop support for Python 3.5 (it is end-of-life). * Fix parsing of multi-line strings in YAML blocks (#111) * Remove unmaintained i18n support. Version 3.0, Released January 10, 2020 -------------------------------------- * Drop support for Python 2 (it is end-of-life). * Add support for subtests. * Run a test suite with ``python -m tap``. * Discontinue use of Pipenv for managing development. Version 2.6.2, Released October 20, 2019 ---------------------------------------- * Fix bug in streaming mode that would generate tap files when the plan was already set (affected pytest). Version 2.6.1, Released September 17, 2019 ------------------------------------------ * Fix TAP version 13 support from more-itertools behavior change. Version 2.6, Released September 16, 2019 ---------------------------------------- * Add support for Python 3.7. * Drop support for Python 3.4 (it is end-of-life). Version 2.5, Released September 15, 2018 ---------------------------------------- * Add ``set_plan`` to ``Tracker`` which allows producing the ``1..N`` plan line before any tests. * Switch code style to use Black formatting. Version 2.4, Released May 29, 2018 ---------------------------------- * Add support for producing TAP version 13 output to streaming and file reports by including the ``TAP version 13`` line. Version 2.3, Released May 15, 2018 ---------------------------------- * Add optional method to install tappy for YAML support with ``pip install tap.py[yaml]``. * Make tappy version 13 compliant by adding support for parsing YAML blocks. * ``unittest.expectedFailure`` now uses a TODO directive to better align with the specification. Version 2.2, Released January 7, 2018 ------------------------------------- * Add support for Python 3.6. * Drop support for Python 3.3 (it is end-of-life). * Use Pipenv for managing development. * Switch to pytest as the development test runner. Version 2.1, Released September 23, 2016 ---------------------------------------- * Add ``Parser.parse_text`` to parse TAP provided as a string. Version 2.0, Released July 31, 2016 ----------------------------------- * Remove nose plugin. The plugin moved to the ``nose-tap`` distribution. * Remove pytest plugin. The plugin moved to the ``pytest-tap`` distribution. * Remove Pygments syntax highlighting plugin. The plugin was merged upstream directly into the Pygments project and is available without tappy. * Drop support for Python 2.6. Version 1.9, Released March 28, 2016 ------------------------------------ * ``TAPTestRunner`` has a ``set_header`` method to enable or disable test case header ouput in the TAP stream. * Add support for Python 3.5. * Perform continuous integration testing on OS X. * Drop support for Python 3.2. Version 1.8, Released November 30, 2015 --------------------------------------- * The ``tappy`` TAP consumer can read a TAP stream directly from STDIN. * Tracebacks are included as diagnostic output for failures and errors. * The ``tappy`` TAP consumer has an alternative, shorter name of ``tap``. * The pytest plugin now defaults to no output unless provided a flag. Users dependent on the old default behavior can use ``--tap-files`` to achieve the same results. * Translated into Arabic. * Translated into Chinese. * Translated into Japanese. * Translated into Russian. * Perform continuous integration testing on Windows with AppVeyor. * Improve unit test coverage to 100%. Version 1.7, Released August 19, 2015 ------------------------------------- * Provide a plugin to integrate with pytest. * Document some viable alternatives to tappy. * Translated into German. * Translated into Portuguese. Version 1.6, Released June 18, 2015 ----------------------------------- * ``TAPTestRunner`` has a ``set_stream`` method to stream all TAP output directly to an output stream instead of a file. results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-stream`` flag to stream all TAP output directly to an output stream instead of a file. * tappy is now internationalized. It is translated into Dutch, French, Italian, and Spanish. * tappy is available as a Python wheel package, the new Python packaging standard. Version 1.5, Released May 18, 2015 ---------------------------------- * ``TAPTestRunner`` has a ``set_combined`` method to collect all results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-combined`` flag to collect all results in a single output file. * ``TAPTestRunner`` has a ``set_format`` method to specify line format. * The ``nosetests`` plugin has an optional ``--tap-format`` flag to specify line format. Version 1.4, Released April 4, 2015 ----------------------------------- * Update ``setup.py`` to support Debian packaging. Include man page. Version 1.3, Released January 9, 2015 ------------------------------------- * The ``tappy`` command line tool is available as a TAP consumer. * The ``Parser`` and ``Loader`` are available as APIs for programmatic handling of TAP files and data. Version 1.2, Released December 21, 2014 --------------------------------------- * Provide a syntax highlighter for Pygments so any project using Pygments (e.g., Sphinx) can highlight TAP output. Version 1.1, Released October 23, 2014 -------------------------------------- * ``TAPTestRunner`` has a ``set_outdir`` method to specify where to store ``.tap`` files. * The ``nosetests`` plugin has an optional ``--tap-outdir`` flag to specify where to store ``.tap`` files. * tappy has backported support for Python 2.6. * tappy has support for Python 3.2, 3.3, and 3.4. * tappy has support for PyPy. Version 1.0, Released March 16, 2014 ------------------------------------ * Initial release of tappy * ``TAPTestRunner`` - A test runner for ``unittest`` modules that generates TAP files. * Provides a plugin for integrating with **nose**. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640753247.0 tap.py-3.1/README.md0000644000076500000240000000704200000000000012703 0ustar00mattstafftappy ===== [![PyPI version][pypishield]](https://pypi.python.org/pypi/tap.py) [![Coverage][coverage]](https://codecov.io/github/python-tap/tappy) TAP logo tappy is a set of tools for working with the [Test Anything Protocol (TAP)][tap] in Python. TAP is a line based test protocol for recording test data in a standard way. Full documentation for tappy is at [Read the Docs][rtd]. The information below provides a synopsis of what tappy supplies. For the curious: tappy sounds like "happy." If you find tappy useful, please consider starring the repository to show a kindness and help others discover something valuable. Thanks! Installation ------------ tappy is available for download from [PyPI][pypi]. tappy is currently supported on Python 3.6, 3.7, 3.8, 3.9, 3.10, and PyPy. It is continuously tested on Linux, OS X, and Windows. ```bash $ pip install tap.py ``` For testing with [pytest][pytest], you only need to install `pytest-tap`. ```bash $ pip install pytest-tap ``` For testing with [nose][ns], you only need to install `nose-tap`. ```bash $ pip install nose-tap ``` TAP version 13 brings support for [YAML blocks](http://testanything.org/tap-version-13-specification.html#yaml-blocks) associated with test results. To work with version 13, install the optional dependencies. Learn more about YAML support in the [TAP version 13](http://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13) section. ```bash $ pip install tap.py[yaml] ``` Motivation ---------- Some projects have mixed programming environments with many programming languages and tools. Because of TAP's simplicity, it can function as a *lingua franca* for testing. When every testing tool can create TAP, a team can get a holistic view of their system. Python did not have a bridge from `unittest` to TAP so it was difficult to integrate a Python test suite into a larger TAP ecosystem. tappy is Python's bridge to TAP. ![TAP streaming demo][stream] Goals ----- 1. Provide [TAP Producers][produce] which translate Python's `unittest` into TAP. 2. Provide a [TAP Consumer][consume] which reads TAP and provides a programmatic API in Python or generates summary results. 3. Provide a command line interface for reading TAP. Producers --------- * `TAPTestRunner` - This subclass of `unittest.TextTestRunner` provides all the functionality of `TextTestRunner` and generates TAP files. * tappy for [nose][ns] - `nose-tap` provides a plugin for the **nose** testing tool. * tappy for [pytest][pytest] - `pytest-tap` provides a plugin for the **pytest** testing tool. Consumers --------- * `tappy` - A command line tool for processing TAP files. * `Loader` and `Parser` - Python APIs for handling of TAP files and data. Contributing ------------ The project welcomes contributions of all kinds. Check out the [contributing guidelines][contributing] for tips on how to get started. [tap]: http://testanything.org/ [pypishield]: https://img.shields.io/pypi/v/tap.py.svg [coverage]: https://img.shields.io/codecov/c/github/python-tap/tappy.svg [rtd]: http://tappy.readthedocs.io/en/latest/ [pypi]: https://pypi.python.org/pypi/tap.py [stream]: https://github.com/python-tap/tappy/blob/main/docs/images/stream.gif [produce]: http://testanything.org/producers.html [consume]: http://testanything.org/consumers.html [ns]: https://nose.readthedocs.io/en/latest/ [pytest]: http://pytest.org/latest/ [contributing]: http://tappy.readthedocs.io/en/latest/contributing.html ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.6908352 tap.py-3.1/docs/0000755000076500000240000000000000000000000012351 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1429298097.0 tap.py-3.1/docs/Makefile0000644000076500000240000001514600000000000014020 0ustar00mattstaff# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/tappy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/tappy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/tappy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tappy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1640756615.691138 tap.py-3.1/docs/_static/0000755000076500000240000000000000000000000013777 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1429298097.0 tap.py-3.1/docs/_static/.keep0000644000076500000240000000000000000000000014712 0ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1454552033.0 tap.py-3.1/docs/alternatives.rst0000644000076500000240000000333500000000000015610 0ustar00mattstaffAlternatives ============ tappy is not the only project that can produce TAP output for Python. While tappy is a capable TAP producer and consumer, other projects might be a better fit for you. The following comparison lists some other Python TAP tools and lists some of the biggest differences compared to tappy. pycotap ------- pycotap is a good tool for when you want TAP output, but you don't want extra dependencies. pycotap is a zero dependency TAP producer. It is so small that you could even embed it into your project. `Check out the project homepage `_. catapult -------- catapult is a TAP producer. catapult is also capable of producing TAP-Y and TAP-J which are YAML and JSON test streams that are inspired by TAP. `You can find the catapult source on GitHub `_. pytap13 ------- pytap13 is a TAP consumer for TAP version 13. It parses a TAP stream and produces test instances that can be inspected. `pytap13's homepage is on Bitbucket `_. bayeux ------ bayeux is a TAP producer that is designed to work with unittest and unittest2. `bayeux is on GitLab. `_. taptaptap --------- taptaptap is a TAP producer with a procedural style similar to Perl. It also includes a ``TapWriter`` class as a TAP producer. `Visit the taptaptap homepage `_. unittest-tap-reporting ---------------------- unittest-tap-reporting is another zero dependency TAP producer. `Check it out on GitHub `_. If there are other relevant projects, please post an issue on GitHub so this comparison page can be updated accordingly. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640755639.0 tap.py-3.1/docs/conf.py0000644000076500000240000002044400000000000013654 0ustar00mattstaff# -*- coding: utf-8 -*- # # tappy documentation build configuration file, created by # sphinx-quickstart on Tue Mar 11 20:21:22 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os sys.path.append(os.path.abspath("..")) from tap import __version__ # noqa # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", ] # autodoc settings autodoc_member_order = "bysource" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = u"tappy" copyright = u"Matt Layman and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "tappydoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( "index", "tappy.tex", u"tappy Documentation", u"Matt Layman and contributors", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("tappy.1", "tappy", u"a tap consumer for python", [], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "tappy", u"tappy Documentation", u"Matt Layman and contributors", "tappy", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1526390586.0 tap.py-3.1/docs/consumers.rst0000644000076500000240000001112600000000000015122 0ustar00mattstaffTAP Consumers ============= tappy Tool ---------- The ``tappy`` command line tool is a `TAP consumer `_. The tool accepts TAP files or directories containing TAP files and provides a standard Python ``unittest`` style summary report. Check out ``tappy -h`` for the complete list of options. You can also use the tool's shorter alias of ``tap``. .. code-block:: console $ tappy *.tap ................F.................................. ====================================================================== FAIL: - The parser extracts a bail out line. ---------------------------------------------------------------------- ---------------------------------------------------------------------- Ran 51 tests in 0.002s FAILED (failures=1) TAP Stream ~~~~~~~~~~ ``tappy`` can read a TAP stream directly STDIN. This permits any TAP producer to pipe its results to ``tappy`` without generating intermediate output files. ``tappy`` will read from STDIN when no arguments are provided or when a dash character is the only argument. Here is an example of ``nosetests`` piping to ``tappy``: .. code-block:: console $ nosetests --with-tap --tap-stream 2>&1 | tappy ...................................................................... ............................................... ---------------------------------------------------------------------- Ran 117 tests in 0.003s OK In this example, ``nosetests`` puts the TAP stream on STDERR so it must be redirected to STDOUT because the Unix pipe expects input on STDOUT. ``tappy`` can use redirected input from a shell. .. code-block:: console $ tappy < TestAdapter.tap ........ ---------------------------------------------------------------------- Ran 8 tests in 0.000s OK This final example shows ``tappy`` consuming TAP from Perl's test tool, ``prove``. The example includes the optional dash character. .. code-block:: console $ prove t/array.t -v | tappy - ............ ---------------------------------------------------------------------- Ran 12 tests in 0.001s OK API --- In addition to a command line interface, tappy enables programmatic access to TAP files for users to create their own TAP consumers. This access comes in two forms: 1. A ``Loader`` class which provides a ``load`` method to load a set of TAP files into a ``unittest.TestSuite``. The ``Loader`` can receive files or directories. .. code-block:: pycon >>> loader = Loader() >>> suite = loader.load(['foo.tap', 'bar.tap', 'baz.tap']) 2. A ``Parser`` class to provide a lower level interface. The ``Parser`` can parse a file via ``parse_file`` and return parsed lines that categorize the file contents. .. code-block:: pycon >>> parser = Parser() >>> for line in parser.parse_file('foo.tap'): ... # Do whatever you want with the processed line. ... pass The API specifics are listed below. .. autoclass:: tap.loader.Loader :members: .. autoclass:: tap.parser.Parser :members: .. _tap-version-13: TAP version 13 ~~~~~~~~~~~~~~ The specification for TAP version 13 adds support for `yaml blocks `_ to provide additional information about the preceding test. In order to consume yaml blocks, ``tappy`` requires `pyyaml `_ and `more-itertools `_ to be installed. These dependencies are optional. If they are not installed, TAP output will still be consumed, but any yaml blocks will be parsed as :class:`tap.line.Unknown`. If a :class:`tap.line.Result` object has an associated yaml block, :attr:`~tap.line.Result.yaml_block` will return the block converted to a ``dict``. Otherwise, it will return ``None``. ``tappy`` provides a strict interpretation of the specification. A yaml block will only be associated with a result if it immediately follows that result. Any :class:`diagnostic ` between a :class:`result ` and a yaml block will result in the block lines being parsed as :class:`tap.line.Unknown`. Line Categories ~~~~~~~~~~~~~~~ The parser returns ``Line`` instances. Each line contains different properties depending on its category. .. autoclass:: tap.line.Result :members: .. autoclass:: tap.line.Plan :members: .. autoclass:: tap.line.Diagnostic :members: .. autoclass:: tap.line.Bail :members: .. autoclass:: tap.line.Version :members: .. autoclass:: tap.line.Unknown :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640751155.0 tap.py-3.1/docs/contributing.rst0000644000076500000240000000257000000000000015616 0ustar00mattstaffContributing ============ tappy should be easy to contribute to. If anything is unclear about how to contribute, please submit an issue on GitHub so that we can fix it! How ----- Fork tappy on `GitHub `_ and `submit a Pull Request `_ when you're ready. The goal of tappy is to be a TAP-compliant producer and consumer. If you want to work on an issue that is outside of the TAP spec, please write up an issue first, so we can discuss the change. Setup ----- tappy uses the built-in `venv` module. .. code-block:: console $ git clone git@github.com:python-tap/tappy.git $ cd tappy $ python3 -m venv venv $ source venv/bin/activate $ pip install -r requirements-dev.txt $ # Edit some files and run the tests. $ pytest The commands above show how to get a tappy clone configured. If you've executed those commands and the test suite passes, you should be ready to develop. Guidelines ---------- 1. Code uses Black style. Please run it through ``black tap`` to autoformat. 2. Make sure your change works against main with unit tests. 3. Document your change in the ``docs/releases.rst`` file. 4. For first time contributors, please add your name to ``AUTHORS`` so you get attribution for you effort. This is also to recognize your claim to the copyright in the project. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1469904688.0 tap.py-3.1/docs/highlighter.rst0000644000076500000240000000115700000000000015405 0ustar00mattstaffTAP Syntax Highlighter for Pygments =================================== `Pygments `_ contains an extension for syntax highlighting of TAP files. Any project that uses Pygments, like `Sphinx `_, can take advantage of this feature. This highlighter was initially implemented in tappy. Since the highlighter was merged into the upstream Pygments project, tappy is no longer a requirement to get TAP syntax highlighting. Below is an example usage for Sphinx. .. code-block:: rst .. code-block:: tap 1..2 ok 1 - A passing test. not ok 2 - A failing test. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.6922681 tap.py-3.1/docs/images/0000755000076500000240000000000000000000000013616 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1469904688.0 tap.py-3.1/docs/images/python-tap.png0000644000076500000240000006366400000000000016446 0ustar00mattstaff‰PNG  IHDR‡‡<çJ bKGDÿÿÿ ½§“ pHYs  šœtIMEà)q¡ IDATxÚì½wxÕ•ïÿyËéçèõfÙ’%ËWŒpp7˜fŒ1Ä‚MrIB&7äNîL2¿ü LH2C À ¸„b0؃qo²%Y²¬ÞΑNïo¹˜£±lu`æ7¿;ûyÎ#=oÙïÞëýîµ¾k­½÷+èºÎ@%ðê«4¬Z…øï‡¾ <ËÈÊýÀ4`ì»ï=tˆÖGAú÷k6ëGXÿ•À§åõõ´?ü0¾-[.lÿ^àjòò±råõÌœàÄ p¹sà 4Ìž}q¿~ø€q€GŒ£GSvâusæ;sáüµiÀ wm?ÌJÉ&méR ÿô'Σ'©k¦ÇF(›îPœï|‡¼_ÿzÀDþo-íí³Ùµë¼^kêPÃ÷¾wñU|ïÿV =™Dý mêðÀ ëÒ=ÿrê×uÐ4´H¤ÿú««Wð»ß½ŠÁ`E@íuÅǹ¯úµ@t-üRd£…ÃçëO$¾ÙG£ƒ^/viÂr|0¥6é~úéÏÕÀì<:ÀX\ ¢H΃ö ô‹ªA@t:I»õV %%ý·ÿܹelÛ¶™»ï¾Q _ŒÇú«_äŒ “‰ôÀÞÙ‰ðËÆ瘬ë¤þÏÅ9&ëú§ƒÔžs\\¦LÙÃq'ûöÝ<ÑÇ­>`Üd]÷ P÷çâ“u}Ö mÿ\œc²®ß3œdþ»œ/Ë0›åÿ-Œ‚C…Èçx^dõÇ?GýÁ!Ô¯ô{2?ýüƒÔøìº‘R‚Á.~Ù$¾hpÌ®»ðÀøÇ¿<£( ²,c³Û€H4J"Ç`0 Iñøùwl2™.¼}0v Nx≫Âá0Šªb2‡ÛŸ‡€¦.(þÙÏJF(\ ðO@x€ºM€}8•Æ dIÂf³?¤íy½î½HÆŠª¢$“˜ÍfÌf3º® ‡QÙ`˜ÝGýgM#å?þà•W^aÏž=L˜À¾óÌÀ¡êjþòüóX,6lØÀج,UWÓíõrýܹ|tø0ª¦õBÕçëÛmE"‘ï9ºuë[TÄÞO?E’¤á ¿~êOÉåú|Œ€ú‡[·ªªÌ›;—º¦&ž{î9®›9«ÕЦiÖ¯’(rͬóå£#GˆD"2­¤„ÓÍÍ<÷ÜsÄãqxঌM@×ùÅLWWË—/çÆoøøJ¿š#Ñúm¼Õ*ÆŽ?ÎÔ©S¹üòËùÙÏ~ÆÉgžaÿ®]üõ¯eRQÝõõlÚ´‰×^x;vPœ™ÉG;v0g nßN"‘@„U˜,ÓÝÝÍoû[®˜8‘l“‰÷þö7dY&™LKè‚ `0½FÓ´~ë6‚À`„]EAUw6%IB’¤žz“É$ÓÆŽåÔ¾}üê'?Ayè!222Peàð‚®c4™=~<‚ °k˲²²¸fÚ4Nœ8ÁÒ¥KimmeýúõŒÏÏÇëõrÓM7ñÑGqõÕWóÓŸþ4UU(¢õoéåòòÚ~-\sóDžzê)$IbãÆ|øá‡Üxãlß¾U«V±yóf^zé%œN'Ï<ó Ë–-ãwÞá@’$‰&“ Q‡$I˜Íf ‚ `4ÉÌÌdìØ±Ã#6‘µµµƒ Ød21þ3_X4M£ªªŠd29h»5M£¤¤„ìììÇq»Ýtvv"B/ ¤€l6›1™LȲ}:3fÌ’©­­å­·ÞÂ7ˆùJÑ4 ‹ÅÂ׿þuªªªX²d ---=À¸Pc,]º´k×®eÿþýÜ{ï½´$Zú‡ýÿPï¿ÿ~ øÅ/~Á#<‚Ëåâõ×_gÞ¼y=‰F£<ýôÓlذÚÚZ-ZDMM .— —Ë5l³p±†[†ò<]×3fL¿ç‹ŠŠ†üìÁÌÀ…ÚqüøñÜyçX,–õíB ©ªŠÑh¤ªªŠÅ‹÷ÆÅcëÖ­=Üð…^ ¨¨è<}àõ‹@v¿pXK›6mÛ·o'??ÿ€\sÍ5ýdÙ²eœ={–µk×’™™I"‘‘Së#½Ç`00zôèÁ!Š_Nê)''‡Ù³gxÐ$ ²³³q¹\œ8q‚Å‹_bJV¬XÑË”˜Íf6lØÀ3ÏjjjX´hÑ€îê–-[0›Í¬_¿þñÌ3Ïððãj*²$áp˜k¯]΋/¾ÀÝwßÝ;v\ÂA.Èí·ßÞõë×S[[ËÂ… q»ÝÜwß}Ÿ‹¤~^÷Ïét’••ÕëxGG]]]½Žåçç.Þ¡ë:ÙÙÙ̾tv‰D‚X,6(@âñ8ùùùÜwß}TWWŒåË—÷˜’ 6ðì³ÏRVVÆ[o½ÕŒ 6`µZùéÏ~Š f‚ÄÁ\¯ÎÎzî¸ã^zé¥^Iy1ý‘ÔíÛ·³råJb±7nìebR)Iý\áoU¥°°ð’øEÊ,õJ¦X,äææÙU½˜Ð~ó›ßdÆ \r¾££¿ß? –KiŒuëÖQ]]ݯWòᇲtéÒòy1Ç(--åÙgŸeÆ X,¶lÙÂü¥ó‰2áGŒÖÔTòâ‹/rÇw°iÓ¦A5H_äb/æBò­At]¿Ä…M&“>¹ÊpâsŠÂÂÂ>M ÀÇ< ŸI$äçç!ãÙgŸeýúõ˜Íf¶mÛÆ’%K8üÑáÏìÛÁ’$sàÀ6mÚĪU«z4È×¾öµ’Ú—‰é¤®_¿žšš,X€Çãaݺudee‹Å>W°l¨Àè‹oƒAÂá0Á`ð’Ñ4zôèÏÅ;ú2ko¿ý6•••—xmÉd’D"A4í1%gΜé1%?úÑúå}¹«cëÖ­,X°€ßÿþ÷œª85h¿ä# &L˜Á† E¨ªJ(âŽ;î@×uîºë.îºë.V¯^ÍöíÛ{Ü\AxôÑGyíµ×ú µ<óÌ3=¡öû￟††:;;yòÉ'‡íûçÅdff^Â7<ÑhEQðù|X,–žsyyy¤¥¥ …†åҦȨ®ë=²kjjâèÑ£466b4{Cª¿·Þz+¹¹¹¨ªJYY•••,Y²„ÖÖV~øÃòóŸÿ¼ß\É@À°X,lÞ¼™¥K—â÷û¹êª«°èª’U#‡ªª¸\9¸\½…¹zõjîºë.Ö¬Yƒ®ëÜyç=yì±ÇÐu½W$ER/ÎÅ,Z´ˆwÞy‡ñãÇãt:{ ë‹.Š¢PPPpɈIñ×Eiw³ÙLnn.>ŸoÈñ€ææf¶lÙ‚(Š(ŠB<ï! דêoYYyyç§lœž|òI^~ùå/Å…ÍÎÎ&33sØ÷æææ’––Öžÿ¢‹(Š„ÃaöïßÏ7ÞÈúõë ƒ½¼’½{÷ö ŒÒÒRvìØÑ/0^zé%ªªªz n]ׇğ!¤Ð_ŒÆl6süøqA`ÕªU=¹¤ögbRä†nàµ×^cݺuTVV²yófEùR²¡)sxñË ‡Ã¼ûî»=¦Ìd2±páÂ^&Àd2‘——‡×ëýRÀ!ªª²{÷n?ùÉOz4ôwÞÉÞ½{{ÜÕ‹³«}iŒ­[·ö£²²²ï-ê sšeàãþNŽ+%“z¿ym³Ù̱cç—QÜqÇ‚ÀwÞÙËÄ\èÅ\¨A/^ŒÍfÃd2qòäI^ýõ/-E>ßp»Ý}úKmŸÉdâ£>BUU.\ÈÞ½{Ù»w/K–,aëÖ­½€1nܸ5Æ‹/¾HUUU¿q…L1Ó œóú;¹v­íG»vi?¨CdÕªUÜyçÜ}÷Ý=ÿïØ±ƒ¥K—öäÑGå½÷ÞÃn·sêÔ)¶mÛ6`Þ¤/òvq°n 0´®ëX,–>§z½^DQì™ÁH$ðù|—€cܸqìÞ½]×/yV_|d°dýi“Éć~Ø£}F#óçÏ¿Dc\ ŒT€ëBSÒ0"Z„9¦9Ÿ7Œ”s I‡^ TäB²sçÎ3sæLV­ZEEE[·nE„!Ãï÷sâĉKLÆ@“˜u]Çl6SWWwÉsªªªz™ A¨¨¨è™ö¡÷a2™.9.I—˜Ç3bMx!@n½õVî¿ÿ~þøÇ?öF*Wr¡W2˜ÆNT>¿r²ÏѰ:t±‰.á [·nåå—_æÆoääÉ“lÛ¶mÈÀHÐŽŽ6oÞÜÇôc¿õ¤–=lÛ¶í’Š,˽F¾Á` ¢¢‚ãÇ_2ªS3Ò/¾ÿرc>|¸O"?R’21 .¤  € &ðÆoôhŒT®äBŽ1 `üûkîGéÊ+Wº¾p\A.‰¤F"Ö­[ÇW\AEEŰq¡Ð/Z(5äb4Ñ4­ç—Šb”XKµq v^ °/ФF>üðCAà§?ý)?ü0N§“7òàƒö„Ä—,Y2\ÑSV¦¯ì¿_ÿüÏy_8©ª¬¬Äï÷³zõjDQdõêÕìÞ½›uëÖ Û”|!sUUÑ4íü =›­'J˜––ÖCŠS/7˜X,F8Æçóá÷û D"4M뵬àË,) òÁ ë: .䨱c<øàƒF¶lÙÂ’%KØ´iÓˆ€ðÏyÿÜ?8Š‹Ïôçü¡i—!Šâƒ9)á †WpÕªUŒ5йsçrúôi^{íµ>Éçpž1Ô¼†¦iØív (..¦¨¨ˆÜÜÜ 1 ÒÞÞNCCõõõtttÇ{&!}¥?ȲÌ'Ÿ|‚,ËÌŸ?ŸÇœÒÒR–-[F4¥±±«Õ:d°¦P\]Ü¿Y,‰Dðù|CVå)p477£( díÚµ:8ì4ú…£8#_–eV¬XÁ¸qãˆÅb^ŸH$p8½@×ÖÚÂSOý–W¶¼MTË'»ør\¹åLŸœÇäâ Nœ¬"ôÓÖÜF<¥,[§PlÃ%A¶MÄ«iŠšPLé”OBR„ãk&³_T ±¹SÇSSý1£rÜÿÝÜsï:,fk¯¶Åb±AÝY³ÙLMM ¯¿þzÏü/SÆ)9ç§å“½6›åõË8|±ØùÉO~5,t ‚@zzzÏhJ‡SùMÓxå•W˜?>¿ÿýï/±ã©ëTUeƌ̟?‹ÅB$æÉ_ÿ3¿ÿÓËì™rÅrìö,DÂÌ)3àPÜt56B"Bv—“h$D}C'ÍÏ—ŒIБ%ƒ ÔŒt  V¢>Y.3’#‡´¬Œ–4È?=ʧÞ&?þçÿü¬Y³8¿nå­·Þ¢®®“ÉÔçŠ}EQøæ7¿Éž={XµjUùª¼†+ã 5Öây‹yøí‡YP³ B:P%ñxŒE‹‹‡ÌðS„ô[ßúO=õÔÈ’$Ix½^üqdYF×õKF^j‹Á``ñâÅ= …ÞÚù&?xäÓq2÷êo0cÆd¦L.@ˆx1Æ#˜¼5(áFgƒÃéDÍÄc1Nœõâ‰hi“gt¶‘Âl;%„Cõ3Û¤á3gP-:h;×ÊÇûkq¤™?¾”Ù³JÉrLF6»¨?×À·¾õ3þíß^â‰'ž`Ò¤)ÜqÇìÚµ‹}ûö!IÒ%f5™L¢ëzÏøþ÷¿Ozzú óYF*ã Ÿ;uìTTmðÄÛ `° ÐÅ E±§Ã©{kx_ϸøX2™Äétrë­·R\\L"ç?ø>Ͻ´“k®]ÅC——Q’c ·ÔBÔsŠ„Gê®B xÇ”¤Š Å’8£:s2ºZ쯑Pˆx)“Kny9É §d¶¥›PwŒü ;Y™xõ’f3›Þ8ŽÑ `Š{)+°“íZCMÍ®¿~9ÿð?`Ãúo³téR²³³Ù¹s'º®÷2ýõw0-0R$ë#åNUm¥®M‘«¡Þ;Øu‰D‚ÌÌLî¸ãrss9w®Ž5kÖÐÔ.óݯ?̬ÌÑ®ãÄbž£h¹t¶´aò·áÐ"˜´$š–D «( M±‹’B‚kÇ)*ÅöPÊ$²ò4yÙè6 ¿7€ÞÕ†h6ârØðvGùÛ''ið(X¬v i‰²¬rLgÔ¨©Ä“Åüý#¿äàþ<õô³Ìž=›ÍƶmÛP¥_Ž0TYTÆÃ ü—ØM0™L’••Åš5kÈÌÌäÓO?á¶ÛW“Wt?þÆu5xÜÌJAM’Ô̸[[éèð1!M%ò“•cC“4T]BŒ…ˆ»£ÔÅÍxTAIm²fÑdv~pŽ’"½o@A¦Î F»'ÆáÆU­Ý&4A$+Ç…É`ÀQÖéð™ÈM±Ëm¨²“ Ó¿Îß¶mæìÙ¼òê&Nœˆ(ŠlÙ²e@€ü©Èÿ€át:Y½z5™™™¼÷Þ.n¾u5—_y7÷/¼ Ý_CR÷“i S˜kFÔMbÉâ /M àq2ÙHtÆH$b$1 ÅðÆáÓŽÞPˆ„Ç"w0c–ƒ×åà ì¯ ðEéÄ %5dƒ•¸&!ë"]%/ÝŒ¦ëäe§s¶Ñ?‘ ±K¢ ]$KêÄï 3füœ¨øˆ ®gûöŒ?ž[n¹…Í›7®½9þŸ¹jV«••+W’ÍÇÄŠ›V1oá·Y9·Œtíš!†,ix;còD$£LÀ±xŸ'À¹Ö(­!A#Í(ÒðÑHÐÓQe#þ`ƒÁL^Ž](.pá4zq„X3cdÙÁ$k¼tJ Þ+c‘%š€ßë#šP(+/¤8ÏA½'†;Å–(+.`Zq.õMÌŽeT{‹–/e×»»™8q"Ë–-ã7ÞøÒfÙÿÿ)›zà 7PTTDmM57Ý|W\{·]QFv²–¤”Ä jD#®œLZ<>*[¼tt…ñ¸¸»ÃD’1¢I›ABÓ øb öL¯œÏ»> ÜEQi>6=@,–¤ÕBêîÄï  ’ž‘O£_¦º-DfF6FQÅ H˜ŒBA•ÊSÍdPHÁa±¡Åb ’â,2]&êZƒÄc×S}|;·ß~o¿½‹Y³fáñxسgσc¤ædîܹL:•P(È-·®¤xÂM<°ä ’gH¨a‚á¡pM”i Æhlì¤3 ÐìîÆIWTô„Ž  ø4É :j8A[{'ééN²ò2°»Ò té(+ªª›IÈå"é!äåñöñF‘$=8ÓíhD":QUÇŒ€ª¤ÛÌD*³·Û ºÆs&ùèçÌŠ&.`ßþM|ûÛøÓŸžgÁ‚444ü§,&ÿ/ UUÉÌÌdÁ‚<ô­õtø³øÍú”ÎЩê´ûâ¸;}¸}:Þ6¡Áhœ@8‚šQe ²Å„=ËŒ¯ËƒÃî@×Þ0Z4αÃÕLž:†,—™§»ÕŠ#? £¦ ‰®Q™¼~°™sž$9Yž¢Á€ÑbÀd´à´™Ñ ³ÅH´#„É ‰$¨oðpù쉔”äq¸Â‹Á R>õfþòÜs̽ò*îÿÆÜpà î;úßàè'k˜ ºmùëK¼ôò~ø£ßáRα¯®‹®¨J{[ˆ³u^¢Š†dLbÂL»/ˆ3ÝÅ•WÍÂë  ©de;ñù‚Œ›2žî./§× {¹òšËÈH3é´4 ´·t`ˆG %TÜÞ85u]´y‚4·'‘e3*f› £`b\Y>]Abñ8¢$žÐ芄Ḛ́ÑÜâÇ‘ncûÛ‡‰DCŒ+Ë%/×FRM 92(+šÍþ!×^;Ÿòqå=ýý/ ]×-ÕW€æB-0Ø6Lç RDÍãñð‡È‚ß%ÓçÃÓ>넼]D"aT«™¢Ò, ò39¼ï0 n¸–QÅ£‰%ZÝtAçø¡jìé6ÔSµD’:£Ê ˜4þ ‚FsÃ9êjê0K:ݾ£’Œ.Í™³§©m‰©X iN;Í­nìv;.§‘öÆVB1 _8‰¨ƒÙ¨sõ¼Ëøúw°ñ_^åà*.ŽkAӨɡ`³ÅLzÁh:ݧøÎCÿƒ;ßîÕïTZ`(›ÔŽTÆ)0E[Égÿ4,999Þϑj|VVÖÃ窪’žžÎ¬Ï¶mþéO~Œf,gtñDNV'ÐÃÓES!kTã&•—m£»³ƒ«¿z%e¥…4·{¨®h$== “& ÅØûѧìÞµ‹ÅÈüùYqÛ7ØûÁAÞýã&æÌ,Ãj±´j’‰.¿NCG Wf6Y‚€ÝafÞü+9WßFåÉjfÎ.'33Ÿ?ÀøI“ÈÊÉ¢±¡…úšJÉcd´À IDAT?ûÅzÞ÷0¯m}Ÿ¦¶0ïï­Áå´à ªXI,'åãfóÖ[;yåÕWXuûªž…Ýdff9|>\_»ÝNRK Žgú;yÅÒÌ©S¯œ3uê•#VM©ÊC)ÕÕÕD£QÊËË©®<ÍK/náÚ›ECM-ÑNZŽE’°›èHŒ[ÌÒeó@Q]˜Ï¸‰FºB12²Š3¦•wß?ƵóçÐÙÚŽª„¸ì²2Ž8A§;Èø²ÑdºD ju-L˜4M áë‘;/ÉSÇP}*ƒÕÍžŽ/Ålhi!9\,¹ŒÊË?þÃÿfÅ+¸îºëøêW¿ÊêÕ«‡½[ópeœ*Þ/ zC#°c plèïä7:þÎëõÎñù|Úï)………Àù½¶†>Åb|ûÛßàçÿï?‘?æ*DÝŒ§¥IQR’AÒ‰%%bmnt]E0ê\¹à jj:©9ÓŒA»E¢±¾‘wvî!T99¹Ì˜^ŠŠÂ¸ñ¥Ä‚!ÆE0™t“fOç/¿{–¹s§Ò˜å¢®ö,ÍÍít´xp¥ÙÉœNáØlì3>9JáØb¯˜Jnf-g«ðYŒŒ4•Ñe%œ:YÇè¬t”X”}z{÷_1(1^|á5î}ðVšjšM {‘t³Én0pº²‰§ó*3§ +?ƒA¢­¥Gš1.¢ «MÆa‘$ MÊ%ÝQƯŸü5÷¬ý:‹/æ©§žb÷îÝCÞû|¸2¾0OUZZÊ”)SäAÌJöMíÉà]¼z°†_¼èº¯Ubßc4™9s&Ïýþì®ñ$56k«WÎã£÷qêL¢(ðÇøô“tvtð8}ìåÇ1nÊdŽìý˜ËæN¦ål Å_NVA.‡7pán?$Áf2ñÚó¦«ËÏ´YÓ þŸISKinðóÚë{ñ7º¹lr>üõÓÈ’LéØ">Þ½Ÿúf7{vÀâp ˆ:b‘("•§›i:×Ýf šPhi÷qÛÊëøÆýù×çw³çãSXd ’& Ù9ã8uj'»ÞÝŲ¥Ë˜9s&»ví8†+ã~ ­Jÿó9{¬ ¿*þÃ\&MÓÈÎΦ¸¸˜X,Æ«[¶‘[¶–ܳ¿2‹wÞ9FG»E …ƒˆ&»‹Ü ;?}ä_°¦Á¢¯#æ´RUqš#‡Oc·[åvóé‡ûð"Lž:…‚QÙœ=SKkS#¶4 QU`Ta>§¨~g³®ºO\¡£Ã‹ñÁÞ Lœ«i¦®ñ0š¨#‰‰x‡C"Ó’ŽÃa%‰b0˜LZ<ÊÙFoîÜË­+æ‘›c¹ˆæ¦ôdY²`´äbÓxî¹?±lé2&NœÈÞ½{Ñ4íK_ö*>Ö¿æ˜=ÛòŸŽÔg!$Iâ½÷Þ¡±-JÙW&c²¼úÚ”DŒ¤%¡&P’*‘p])(+@L¤sàÓ<ÿÇ·0‰I–Üp® '*4dÍéÓ§ùÃþ@(êwþ‚¦iØl6F…ª©¼ÿþGL›y=~¿Ÿ††6\N ™,œA{k'^\鯖ç#[ÌÚ_‰ÝeÇb³ bÀßäÊ+ÊYsï­tuvP<6›ÍŽÏ Íé@‰Å1Èqf\5ƒ¯,\†Õa%Љ%8SuŽsµtù`Ûkimk£ ]gþõ³¨8Q‹,«\3·“$’—kÇ™n% "‹2³ cœ®lÄŒâr˜[’ƒ?¥©9HKcª®0ïÊRFgË$•$³ðÖ[oç·zÈ3”$‰P(t‰ŒÃáðÈæˆ ðúC@l8YåÔ&ð•••=ûWVV¸^Ó4ÒÓÓ±Ùlœ­­¥­Ý˔鳨;ÛIGG€ÛîœÏÒ›®ÝD{G›ÍLaa.9Ù™Ôž:Ë÷,dÁòkñùº˜:©)SF‘;ª˜]›· ùPh®«&ìíÀ,È5GzW.XŠ(Ã'ï½O<'¨m覦QAKˆt{<̘Fý‘]ÔÝORhë rå•ÓY°t>^œÆf/¾®¢&c2AÐ5•¤¢‘‘FyyGO5Òê¢ê:6« ]¬Låã²G#`t‘áÈaÿ§ûPU•üüüó-#‘qߤA‡¾”ù™+{'KÎ3™„a›•Â/>Ö_È<!<|è Ž4UF’tFÎãôñzÞØö1&‹Œ$ÁØñ‘¤$GŽÔRZìÂêt±ýÕ÷ ²,“èöðÉ»áóénm&Ù† @WKUGŽPT\Hù´«‰†ì{ë-ì9€Á™‹?‘$P]‰ÆÜ-ün7o¿ýaÓhLV+‡W°pá\N¬¦µ½›ÒÒB‚áᤂ¨ê¨šŠ+ÝD<çí*‘Hè"Œ4—É ŒD±Ø,ØmV| °¤¢áÜI)))!--ŽŽŽ~5ÈpeÜW1 FZÔ–’Þ¿ ü¾¿“[·FX±âË_ìœÚ§ëر£È¶<" •d2ŽÙdåÈ¡.Ÿ[½ëo"KrôÀ1º=®¸r‡>©`ó_Þ¦¶¦ ³Õ@ £O²›š£ÉÍr–ŸÁèÑyHhÄtÊËJ8ñé1ÎUTR{üu5 ø}Q”¤®¨ÌLy6Ú:"xÚ›èllÆb±#X29º¿†QEyy<ýÛM$T(*Ì |ühNŸ®¥ÃÄf1! ‰h$”(£ìÜLt¢a•><¦ët»;‘ fŠGepæl;ƒ…X"IeU%%%%¸\.ÚÚÚ¾T™[ Çǧ ôþåÌLuuÑ'e5™¾ìÉJ‚ ôìýy¶ö º!š³^:ê<ĺ®»v<‘Xgš‰@2DVš…³§}ìzó#:;}hºZŒX8LmHaZž•R›€ÉåÄfOC¨=BɈœ•‹¬'i=yŠh ÄÑÓÕ¸}AÒ²rQ 2aDYFC¢£³›QŨGL.<~ÍdÂÝa÷ûÇIÏ´aµØø#ìÛWEºÓA†C#&ˆ(ñƒLLÕÐeo(ˆ¦ŠäædàqwQq¢EÓpwu‘‘‘Á¬©¹Ô9ÿÉš3g`é2ÒÒÒ¾ôYb::ò1P#Æ€|ùå,8q‚Z[¹q(æb¤×ô÷ Q±Ùl=a`É4—pXC’DŒ&hm÷`’Áç²ã•]ìßWʼn“gÁd Ã.a@ÙBñØL¬.$°'kˆ·4ÓU æ$²Qb$bG=U5Ä:FƒÌ˜Ñ£8{®Q3àvû‰Ç½hªFTO¢j*‰Xˆ&ŸEÕ'âd3@º âÊÌ!¿ ·;Œ%ÍFÄÀd²«¸liø"A ‰“›oÆbsÑÜDì6+ŠÁ˜„úÙwŠÏÕŸIjOÔ¾ä5÷]Z°t¯bÕ­²(âŸ9“պΖ¶6–ôõòdYîoásêøÅ£!¶X,躎ÏÄ”—…ˆJºËA4 »+Θ‚4Î;ÃýµÈf w¯[ÂÔ™“¨õÝ’’óŸŒ衇z–ýõ§ÎÒÓÓ‰Ç㨉¹™&I5á`%% Qß°«QçÄÉ~øí'iªïÆ ‘HÆÐ„­u­dfÙhmj£³ÅÍí×qå¢åtž=‰èÀ’ XÓøê’ù¨º‰§~ù¢.’Élt›Æ¨ÑÙ45¸‰GUtA@ÌUÓPuè†èö'1[,Œ&š:b4·µb”‚á(s®™Íôp”—ÿòiyŒ))&Wèê>?qYMÆqZ ŒŸ8–ŠŠ³XŒj"‚Áj&;癩7®_™_(ãâââ!Ëøâ26w,IåßçsX±z×±î&õ# ¤–D~â‰}´]U%øàƒ!ï­ÇéììdÍš5‚ÀóÏ?ß3“¬¯ÀŽ®ëøý~ÆŒƒÉj¡ÛÝŽ å`2[H HN‡ÏÍbÄYä$-+†3mجª(ÑåöúÑ4•D2„9Ûå_ž{—•ó 1*r—Ü@øè1:ÏV!9óÈÉ/äT‹Nz~!’7ÄþO*0 ñ¸J8Á ‰È‚]°;TTU#='›îî’(N*¨jœHL ©±“Ÿÿø×Ü{ÿí”O)á¹g_eΕӸúº|º÷‚&ÑæöQßáEÓt6ɤ†.ê «hº†Ãq~o‘ÆÆÆ~e~¡ŒS›&ã>³ºWÈÌ“ÎoA« (;Øá“7Q>‹‹5 MÈ>ZǬY"v;ˆ"†½{y¾ºšÕçÍJœ ®%`H«ì n·›Ÿÿüç½Ï}÷ÝGNNNŸÛQ¥Ç•µÛmCn¬Ù"QASH$ⴶʼnFL$5…qKˆÅ#\uýtì6™–ÆvL¢ŠŠ•.o„.O‚¸&ó†8Z¥m­çúYù”æÇYpß·éêì ©¦–êÆýh¢‡U$;s±Š(›ËA( Œ&‰4—•.MHêqüIUÃl4aDâH$t 8AFšg~ów¯¿™»î¹…­›ßà+ó¯Äl4%1(‰f³‰P$Žj„L§•¨?DT‹ö'NœÈƒ>ØçÔLƒÁ@gg'>úh/ð|ãß ;;{H4J&“L)™Ò³Ê^E$ø°8°'°ÙdèîF~õUž?uŠ;/~y‰DbHšCÓ´>×a$ âñxŸÉ¡D"A( ° €Ê¦ÆN6aÑ3ð{T\.ZDvv:9¹ ÇdárıdæÑÒÑÎes®EÒ4bÎïÆÝîáøÉj¦\>§'×eÂîrDZX¬´¸ÛD#’$¢):N³%G4ÒÓ„£aâ±ñ¸‘œ‚ ^~î Ö®¿•Ù³góÆæ=Xí6’‚…¼táö³‰ô4‰X‹$"Ê:* F¶UU0ì7>÷Žd2‰™^¾ØÞñ³™`‰òï~Ç¿VUõÆDI™€ò²r>>°‹Âl‰qyY˜l£)(L§êÄYŽImm㦔Q8¦cŸ!=#‹Ãû°ÿ“˜Œ&ÚÛÚQt•t‡ƒdÀj5 fTM'U˜XšOss'MW°ÛüùÙ­ü?ÿ/Ž:MRS4Iðúˆ‚€ÕhDS’hÉóD´¬l\‡øšÏqQéQ–1ÿùÏ<÷ŸŒTéîî`ê´éÄäe™ðú’aòóÓÈ/È -ÃÏ$Ý™†3ÍF$˜àg¿‘×_yƒ¶v7çêÚ9UÙÎï%K’ˆë„CI jÎøúš¿ç¹/ŽD©©:IgÈh6rõÕ³¹çžÛ¸jÖ4 ²²¸iÅ"®ž7kºÙb¤½³éÓËS˜OG[;ÑH]1DJŠóÉv0H ®»v:‚~~W P8Hw‡Š#'X´âbqYT‰G£dg¤! "ɤŠÑ !ˆø0Ë'NìÇÄ­äM`º¼s'ÛëêøêVKDQÄív0cÖ,’J„H¸›î`‚sµMLš2Š SÇPsºž4»HšÍJz•y×|•úš6ýáMÖܧ©;ŽÝf@E$Ó‘T»ÕLf¶ë¼›,Û8]ÙDåé³äæp÷}\wãRÒ£ˆÄ#„½Ò³òQ%IUãö¯ÝÎʯÝÂу'Ø÷á}|¿/Q¶TTÐV³Èe“ 8SÝBFNƼ ¶oÝÁwþ׃;ZDs£›h0„Õ¦a5ÉŒ.Ì ÍnÁÓÜF «‰üÂJÇ–‹Åþ³Á0 Ø.Œx\g¸Ú-åN¥è\x¬¿¨jww7‰D‚ñ'‘—AuÅA.˜Æ´Ùc°Ú äæàÊt EIªJFe›åbÊôRî{ð.N>‹1æÆÝÜB{s#zÂKÐçÆh4`µ¥‘ž—ÁÜ9åÜzÛuÌœ3ÚÓÍüægÿÆoÿéל8¼h8@0Ò…¢ÆÏÇ$ IÔ0L8fŠ‹³Ñ•¬Œ\1Îéêˆj„±¥E\}Í.¿b*sæÏeñÒyèèÄ•Åcó øB$”ËÍAS¢šF$¤a7‘DÐTh4F0ìfÚŒ˜Íf:;; ‡Ã‚c¸2îÓFèñLú)2ð@go¹Åº,Ðn0L&3gÎìùÝÌ™31™Lý6^EB¡­­­3ïš«ùø“Ý\½|–t;'—"‘$’Y”ÅØ±yìyë[ÿú.›Œ+ÃHn–°1¡<Ÿªªs„ü^lN‚g̘ Òœb±!€Q¹™LšZŠ·;ÀŸŸÚD°ËÃÍ_»ü±c±É ‰Ž(èö´òú+oŠÄ)(ÅÙ3Ÿ•Îâå‹ñuv`³(Z’Â’"º:<$”(.—XŸµ¨¯¯Pk¨ªŠÍf¶Œ/ñnô£¤QçLÙR‡y$f%‹õ44‹ Ö•$‰³gÏ2þ|-^BFº…£‡>A4•rèÀ nºå+Œ*.âøáz²r3˜5÷2vïø„`°›‰åE”–qÿ·ÖòñÞc|òîNæ]=—}{?Äf,dÿ¾“Db‚þ õ•ÕܲînŠÆ•qõõóèhiã+×_‹Ù`$‰NP8qø0~¿óÿiïÌëªïüÿ:Ë]’›{ov²'„-a   ì(¸*ZE§jgj§©¶ê8užÎ¸µ¶ýµótq¬íX6-jQ° [XCØ  YïÍv÷å,¿?nî%ÛMÚÎÌïùçÉ“äÞ“ï¹9ß÷ù,ïÏ©’ß½0f³@ÀéʓŒ*EÅÁcÌ^¼€’ %ø}kj4ãÊFR4²Öv/‰V3——âQÔÕ»Q]²IBS4|…¶¶Zfꉑ#Fàv»ihh4Èy=÷¸?µ2ØþióV¾È!I466’ͽ÷Þû›Þã†;žåƒ0cÆ8,žNrŠ-ïƺÿú›ÝŠA8î§+Nsp×AžÿésŒ÷u>ß¶Ñã‹ÙóéNtUaá=ó¹yÆ$’3†±ë“̘?ƒÑÃó0LO@'í{?/ç|Õ%Z›;9:‰SÆS:f$î Š*c1›Ðt‰öV7.—›ÑãGS0zåÛwS8f$ç)üU'ªÈËÍ$++QHIKaôè­Æç‘lH$är׳rÕH9¨Çã¹î‘!×u 4oeÛ¶â¸é Q16T,Šêîº/ ©+PQQÁ¢E‹øúSßä÷ÿuºÒB{ØÄ¹ª+L9ž³§.ã ©Ì›;…¶öví.Rlº$R{©žÿxùç|å««˜:{£Æµq±ê"e7Œ"#-‰1“Æš‘Ňë>bøè"‚¡Š"àhiMáøÁ Î9ËÅsM´·µSP8Œ_¤b%5êñtlÉFÒÒÓ1[Œ´v¸* ‡Ë+).)å–Å hjl ¹®Ÿ'Àø)À)Yxä¦&“_œ³Í˹ª+¢‚¯³šüœl–.Ì<©¨¨RµüõÞãî$XTêl+ÞGúã‚cÁ‚$† Æøñã‡<9Úk3*KJJz„ïÒ¥—ËÅÔ©ÓXpËlNìÙ÷}7ˆÇí&ö2}n 7ϛĖv‘`K$«0gk¶ôdN¼Â/^ý/¿þBª—‚‘Eˆ‘‚â<|~ͧOp×òXìÉ„|qyT:[ø|A’Ó‡‘¯È˜MF’³ ¨«? øIÏÌÀã®Ç, †Ø³ï“ËÆ“ž™…ÕnçTåQDIdû‡;ø|û–=ºˆÂ’QLžZŠ®8[UÇ•šZÂaÈÍNÁÑàÀßÞL»§šo}çyì6$ RVVö»ÇÝm<»ÝŽ®ë ¯V¾÷½†xÛÅw¿›ÁäÉ“™gÙ#w±ô¡»ØðÖ|v‚ôôTæÜ>Ÿ‚á945BsS=©©©Œ™0š1Š#Ý;½ ÉXívöï)§µÕ…`L–UH(¬ †|ÔV7#‰e3g0eÆ ¼ûÛ÷8y´Šy‹çQW]Gm]3MMÚ<í fF” §°(“Ù€×íçô©Ë¨ªˆ¦ ŒIÅ0íÍ—h9xççëILH@ÓtθLŒ{ä%|+’ª «èÈ"è k]EïÒ/QƒôêïÑ2ƒÁL£ž‹/d£ÌâÄnÔú½×Vƒ•3á3eí¿ <ïÍ}û‚ÜsÏ_?:(ŠbÌÀúÞ÷ŸeóæØõ§ŸrÐlæµ—ßçÉ¿›ÇÂ{g’žžÄ¥ª+d¤˜).-¢|g9»>>ˆ¢ˆØS“6,³Áľ}gQew«‡@X£¸¤UÙ¶e7gž!d‹—Ï%#'‹° ‡Ë£d–>º„´a™¼ó_ï“_˜ƒdLäàÞ ”‚ÍžLBRÓoÇÔ©SHMµRB|>*+ÎÓÒêÁ"eì¶Ä€ƒ“õ‡ùöwžfþ­óq»Ýœköq¸ÕL‚E&V›k£ =?Ìá‹NUí²5ô>é 4­«’M§C·pÚ§0Il'AîËœ#5JMÁ@û? ZùkÔ­ÄKjii¡¦¦†3f°zÍÛL2‰ý¿É‚û¿ÅGïlä¯o#7×FV–…Ûî™Neåt²r³Ø¾ésΜ¨fڌь,)"3;;M§½ÃC¢Y&ox¾€ë·ròèqnž3Qc‡“[0Œv·Ÿ†Z§–0öÑ{q{ü|ôþ'äeò+è‚F‚5™¶¶vd‹ù‹n¡dL>a]'¤°$t’RíØ}!èpc—, †¹p¾œ3§ñï/¾Ô%™·âNC[PD@cl®Z4–‰…)túB¼]^Ã럞Á( =ÁÑýHAÐq(šC~Šd?¾èÀu+C±9þÛQÙ¶m……… >œuïü‘;-V=H홣 ±{ï)N_yðN•£´¬ÀGv–™¦+m˜“, ƒŒž&E —BÖädÆM™ÄœÛoFd’Ó1ÙCdåáioåLåYL›>…ºó9¸¿U”xìoï§¹¡ I°$¢…C8t¸ü Vk2)ii ÊH¦$T¯NíÉíXm~þ°vf“‰#GŽPQQAÖŒÑ%w ÌÝSò˜P°cO4òØœ‘¬ÞyްQ3WÝ–((–(¢(ÐÉ5jäkòþG(âý÷ßç±ÇãöÛïà·¿}ƒ'žxIÒYpÏr¬‰AæÜRJž#´¶51ÿî=\Åœ;o · ‹Q%# ‡4B!?èí.üA?ö¤dB!w-›¢MÑA…"I¾^ß˰‚ŠÆŽÅ’”H§ËÃÍ nœ”LäHN‡¢†hjõ¢‹‘š_HEñxq»ý ($èPuöÿ6}²™¢ÂH‹…­[·"Š|!%ò¤ë:Ц¡ëÄ\T†$¾ÀˆI­ÿ÷º¾u‰°.r=Ý>þG7Ææ¤¾ûî»<ôÐC<þøx<þñ¿V˜÷bs˜áãGa¶Ù‘27Ï›„ßëÃb±¢é:º¨`4JèªFsC &ƒQhÅ’lGÓeBÁ`£)¯Û‹®K„‚~òò  Ih¨Lž~•å•J޶­vr sñy½$Úƒý!L& -í¸:Ýx.¼í>Μ:Œ×{†?¾÷Ó¦ÞDKK ï¾ûn„ ÞÑuL‰u{ª)ͱ1©( U×yñ½£èZ$ɺ_é00t]GeÅë ‡ü©a2™¨©©áÝwßåþûïçþá±X,<õõ§ðzZ™yÛ}X4Žv?i™&RÓÓ@×Ð4Y0à º‘D‹‘d{.·—¶’lÁ˜`!ö!›DlÉÃ$¢)+T%D%lv+SçÞÄ™£Ç1™L¤¦¥ ‡ ùÄ /\ærmª* hm*OV È |¸éOÌœ9§ÓÉÛo¿ÛíÆ`0ÄêFôˆàÂÑéç;8HšÕˆÛÂå "ŒÁŒT=F˜ñÿšä說*6lØÀòåËyâ‰'ÉËÍcÕªGØÔr™¥_}š¬Œ4.]j!ÔHK³`JP Y0%p6u ã&ÔhÂaÙhA2Jdd iaÐD45è„UPƒ$ðû kŒSLØçÅÙÖI8,¢è2ª¦¶Á‹Ç/cM8›;©>¶“â"¿ÿÃ&ÆŒ)Åáp°~ýzZ[[û†"ºm¶7ÂöõDú†>¨äøBªÿ%G ëÖ­Ãívsç]w±¯|?Ey üòß¿ÁÇîÀá óÑGG8~²…ÚË^-~Úœnü?~¿Š&Š˜ìi„4#¢Áˆ¦©}~‚~7Z(„öùñz:ñ{]„ƒ~Üm¸:: ¸;=4^iæJƒ¿/)hióbINÁl´RWUÇ©CrÏ}øô³­ŒSJ]]kÖ¬¡­­­0"ö…Ž®) iñ¯ð]W‡ ­§Ñú—‚ ü· ì —.]â­·ÞbÉ’%Œ9’Ïvîâ•—^äµý„Ìü äÎb›ã4ão,fÄp;ivnŠ-TU§Ýá&Á”ˆ.j€„ ˆhj]WQ5‡4T% ¨Ð4Tt4B A¿NXÑ‘d™æVM4\é ùJ§ï!ÅÚÊëÿù_Y±2>زeK¿3f%QgƈÜŽ+üî·¿å¶Ûn#Ñœ€sM»ÅStQ€&¿LC(I㨛³ù¹ëÞ "Ýoí—6PQ‚Áà5%û¨ª+7Cî}½Ntk÷„–îÇ•+Wxã7˜?>Ó§O矟ÿ–,¹ùçgÙöç7HÏœ„)Q&3u2QB6(^ÁŒ£Å"X­ÉhzQA#5U ¢†uÂa-ìEÕDt4|~ UPÑéìô£)*Íõ.ΞºBõùsø]gxhÅ\¾ÿìw–™Ûíæ³Ï>£¼¼E4¡‹´ÖëÞê$'ÊŒNÖpœ)Ç2k6ƒMS{=ì]·6% ]7ƒ.ökêôä=4M% a‘ݘºÈ0YcZHPìda 0¡~‚:üÚk%x½Ïò ?½f‘í¹áõz¯ùoß~ûm/^ÌÁƒã"\Ó"ÓJKKÉËË‹½¾}û6^{íGìÞuŒay™6o!&—’˜d€°B8ì#9Ù@¢É„d”‘$9ˆÒ4DIF  Uüa!F (º‚˦µ]Åíñ£Z¯Ôs²âá`= 1o~ëï˜0qr쩪ªâXÕ%.ºDªZ5ê] ®€ÚƒÈÒõHU~0ÄåêìiKtç0¢ªBÓ°Ú’0›Íhê@À¸*m:kOQµé-^ý'‰å ‹‘% Ð1MX3l”‡®°Isò¶ÖL#ᡃãßþ­[o=Jyù‘k6nºµ 1° ¦Â$IbáÂ…±üÈë=öíÛÛo¼Á'Û>Ç40¢d#K§’‘“C²ÝˆÕ,#Èš¦#âC$tT5L0¨£ º®áñhwpuºiil§³áí΋¤Ú-œÉ#¯`Lɸ>ׯ¸ÔÆëÛÏSy¹­ËÕ&°èf# @nÅl‰^ï÷ Ðfs£MåÞ™{)©Dƒ5W¯£El™÷”Ëü£r†+‚24µb49qâk×®½fpƒAt]§  €¹sç²fÍšAU‹ ±Ìêììì!µzî Ȭ¬,ÊÊʘ1c3fÌ¢µÕÉÖ­[ØüÑ&*v¾A›+Œ91{j.ÉYX-vlV+†D ¢ ¢¨þ`ŸÛOGG.Wž¶ÍOnNón.áÎ;¿ÉÜy·Ë8vì.— Q©íÔÙT#ҡȈB4/CMA‹|ïïã¡hñ]Ô¸Àèù^cc#>ùœÒ /ã ó#’CïD]g™˜Ç¡ƒõ:ÂÀ¡ª:fs¤êµô>—$‰Y³fa0˜?>‹-ÂçóññÇc0â¶3 ìß¿§Ó‰ßïçСC×ÕÉ·¼¼œ±cÇ2~üxrssY¹r+W®"ðsêä ŽVáÄÉ“\ª©¡µ®•+Þ¾@EÕ1$%&’lO¢8;“âiŒ?²²IŒ))Eå/\¸ÀñãÇ9þ<'ò?è2'Õ":¤Œˆ'MéÓ»¤ƒ¦îm Bn]õH¸ºn×WÌf²èï’½À¨_5^¿. g#íÂS+Óã©•ºº±ÕÔìýÚ®]{†²—ÑŽþ@€-[¶°téRöìÙî]»0™L1£³{>G[[?üáyÿý÷™5k¿üå/¯ ÑÉEF£‘¬¬,Š‹‹>|8999ý&ïªjÄèÖ4Y’1™MBß$(·ÛM}}=ÕÕÕ\ºt §Ó‰ªª=ãÔ‡­T¨£"‰=1ÀÓÿå£çº™f…i)pÏM»™0<€(}v1Išã?—é'Ö]Â!(w“óòä9§O‡bg†šŸ˜˜˜Øu³5–.]ÊÖ­[Y³f +W®Äï÷÷Hw@]Ëug´?V}}=µµµìÞ½«ÕJjj*¤¤¤œœLbb"f³Y–¿¤­½ƒ@ €×륽½¶¶6œN'mmmø|>4MC–åØW,ÑWhV’E½ë¼V` %4•DÄ_·ëgMSQÞ./,Bâ!½ÑókŠ–P\£N¬ GšÓ†k©[‰>EË–-C×u–/_ÎÖ­[™5k³gÏà¶Ûnè _ö!B ôz½¸\..^¼Ø‡Ãéþ9¢3m»÷Ü’$)6^$îƒD‡–€. ödÇ‹°j׌^ÀÒTU×1@’4²Ó ˆ¢Ò/ z©—™+Ð$Y{£ª*²,óàƒ’——Ç}÷ÝÇ–-[˜={67n$99™§Ÿ~»ÝÎóÏ?ÀîÝ»1µvñÈŸÞÆvT×z(ºD›fÁ,F×"0º{+ƒÑáÝω#4UAÕ4U#Ù&`6ø1ÉáÁ¡i ˜7ëû¹ ×ÔŒþR€!I+V¬ //¥K—²yóæÀxæ™gøÙÏ~Fff&_ÿú×ûH/zD7øz€öeÓ«J˜d±k†æi0€§ŸõŒ:ML~Ôaf…lƒÆ„¢&,fep`D™7Å=’üYþîw›â9†üñ…Cƃ>H~~>÷Ýw_`<ûì³¼úê«äää°iÓ&233©ªªêžÔj@AèaF °ºKƒîj#ª2¢ª"ú{w€‚{-ZfØŸ+/ŠbìrrrÈÊÊBUUŒF#7n¤¾¾AHIIaÉ’%¸Ýn6lØÀ”)S˜$—ލëȲˆª¨„•H/1Ð ‡”H(0›Œ¢ˆª„ ‘q –ÒlV,b˜Œ “¢t;'V“iDȯxÀè!¥Õ—†¯{ÞJoãÞ{ï Œœœœ0Ö­[ǪU« 6,f¤Îž=»ßŽªªÔÕÕ‘••Eff&õõõb±X¨¬¬ÄétrÏ=÷à÷û©©©aòäÉ8NL&yyy´´´ºò3òóóÉÏÏgìØ±Øívl6yyyŒ3†œœl6íííLš4‰o¼‘¼¼<Ο?Ogg'yyyƒAü~?yyyFòóó#Ÿ-c#É9ñ€¡÷6>¯¾¯*a ª—‚$oÀƒó&BÓ)ÎîxWU9÷M/áÑ;¦’+wrn÷\>¸•[ÆfñÈÓ˜Z`£zߎmYÍ-2ï¾ü$ËïÈbñ”S”¸FŸ×é¨ÁACsÊ@ÀˆÚýŸÝ±iÓ&&MšÄúõëyä‘GX·n=öåååüò—¿äÖ[oeîܹ=Ä»(Šøý~6mÚD[[­­­lÙ²¿ßO(q±].@€œœÚÚÚØ°aÛ¶mãüùó±$^§Ó+>|8­­­x½^òóócÍ× cjæàÁƒhšÆ¤I“8}ú4;wîäóÏ?ààÁƒTTTpà 7`4 …B];­¯¡Øô†€N‚¬såð'Ü6Lcÿ=ÊcÜÄ¿>^‚µs3Ÿ¯}Š[G;xý·òÇŸ-c”õ$Þð ªö~—ù£Ê[ºv`èWKíd`wû,S§NeãÆœ>}zPïÅ`0ÐÔÔÄ;ï¼CBB³gϦ¼¼œŽŽn¼ñFl6[ŸLUUÉÌÌÄn·SZZŠ$I1ÊÞï÷ÓÐÐ@FFF¬‚ÝétÐÞÞ·_Æ'Ÿ|‚ EIPIK2 l4öÎ×–LMz° ëB ±ã·ä¥ÉKËŒœnBD§(ÇÙi]Û ªNVšNVjj×zmÒ±%è`¶ö%¸*0"_Í‚–ô›åÝÃ0cÆLæ«_-ˆ¥§§c³ÙX²dIL•|ðÁØíö^ÉæÍ›{è!1-ZDcc#Ï>û,/½ôš¦a·ÛãöÑì¾Aš¦QTTÄÓO?Í™3gHJJâ@Q._¾L[[[²JUUòóóE‘íÛ·“™™É7Þˆ¯kìÅÅ‹)++‹]'ú=Ê‹D%Q÷±¢(ÆØX£A'/-)Ž ÛÅZ>–O/¢®ú3–̃$¯Æ?z‡ïûU ýDsû£Å£®µç¼îŸQÓpÈFT„oš¦’œœIrrzhkŸ/Æl^¾|™Õ«WsùòeL&gÏžÕñ:V¯^×ë%11‘ÊÊJjjjðz½FZZZX½z5N§MÓX½z5‡ƒ÷Þ{/Ö”¾C1R,$a@` ëÈz˜4Á(6R”~ƒ,tm ½€Ñ}£{ÆAâŸ×Fïî2÷·½¨tt>×¢VñfâØ±}ìÝ[ÉW¾ò¸ÿþûc#jctwW£Àxûí·û#ªJyî¹çxñÅÙ·oÇŽÃd2ÑÚÚÚãií~ÔÖÖÆ˜Ëî£5£¬çéÓ§c<ƒÁ€Ó餥¥%3ÖÖV²,¸páBŒÃe™êêê˜4ˆÆS¢ TWWÇÔÑ… e—Ë“$E"AÏG%¡iÕ?ë) y²µØìÞHn¨F¯à›6©1Èyº>ðû½UŽ`àˆ&, )M0ÈìÙ³±X,,[¶¬Obij1¢ªäÅ_dïÞ½|úé§±MhR@w5/¾mýõÝ[1FI2UUci‹¢(ö˜#Iš¦¡ªj¬[€®ë1& Äèß„ÃáØôUU1 $‹~Zuó å’ c6눪ój(?Þ¦ FZ]³Ú‰s Bk¦^ žÏ ¹á†9„B*K—.ãÃ?Œ ŒuëÖÅÜÕÞ6FCCCÌÆˆ#šßq-9#ý3gÎÄn·#çΣµµ•iÓ¦!Ë2áp˜cÇŽát:™7oÙÙÙœ8q—ËÅĉc­ëêêL™2‡ÃÁÞ½{1 Ì™3‡””Ξ=ËÉ“';v,eee8NöìÙCii)YYYìÚµ›tƒg8¹`tß ¡«Å¡Á7íz€¡ݯÓe¨n3iµÁÁ!I~¿ûï0fc|øá‡qmŒ·ß~;Œî£·Ñ_4¦"Ó¦MÃb±àr¹˜ù„–– bªJ×uìr˜"“+’Ü;Û»làªw2„ˆiwuñe£Ç¹¿789#†VÔ$—/×2yòd6mÚÔ/%U%QcÍš5<øàƒ}€ÑÝÆø²M2*((àöÛo§¡¡††t]çÖ[o¥¤¤„ÊÊʘ-5x£Æjw²-j[DÔèyÑRF—ËÕãœÞA=A€Â?©z{W20‘ ùþh‡I½&6óúÏEÁšÆq\¼eê¤SbÝŠßïãöÛïâ{ßû÷>6Fw‚kÕªU=˜Ï¿60¢*°££ƒ>úˆ+W® ìÝ»—áÇ3vìXdYîѦ±w;ªèëÑMöÕ4ÌÌLxàÜnwsºÏŠÅjÔ™hsqÁ«Ò®&â ƒ(zØJ¬X‰!C×ôÐÐ Z] Ó¬8,zù‰¹…#rpèEM~€… #Ý»3Ÿý\ýñ½½’¿0 2I*ênvo=åóùƒdddpæÌ¦M›ÆÂ… hhhˆI£ÑHMMd®ëâÅ‹IOOçøñã$$$0}út/^Lrr2ååå°hÑ"2228yò$ÉÉÉ ¾öµ¯ÑÚÚÊúõ뱦¤xqt„tÉqº@aÔHk…k: m6Ò ïÂçóñûßýž¶V'Ó¦Mãž»ï&³~ýzέ¢°¨‡W>L‚ÙĶO·²oï’’¬¬|èA²²³p´8iJîà_:~Á~C€fé+Þ¢â7%Þ_¬¤»»úeŸ%êìÞ½;¢×4öövvîÜISS~¿ŸÚÚZ*++inn&;;›ŠŠ ±Ùlìܹ“ºº:êêêX½z5£G¦¦¦†C‡Å‚V«5f—´µµÅÎ9xð ………tvv"Ë2~¿¿G«é ³J†èÊèîa') ê=ôþjp l:RÏC+WqߣÓxàøõÚwø›c‰¼öãòLÁsIDATsÿ øêW¿ÊëÜÆÆ]AV¯{Û}”ÏÎ<ÿ¾ú*?}ç7‘=¼¡ŒÃžùÐ÷£¸ÆÅ€EM¯¼2š‚‚?ðÐCÛ'ìþðÃ#Šb¿ª$ ¯×ËÏþsTU´6S’$ÚÛÛùÑ~t]ÙçÁ`065±;ça0bù ÑjTQ# ÆrMÃáp,.}?ÊkD™ÖÞçD³Ý{Û.ƒÑJ€ï<ž…EÝÑ“vÀnðE\žˆëŸ‘–F0äÂùó„‚AÒÓÒÈÏÍEÓ5.ÕÔô¹¨jLâ–‡×aO{ÈsssÙ¾i;õãêYpvA\pÈ@gü’FÉ´ÿsJJJUòÈ#ĵ1¢Àˆ‰Qbé/}ôÞQcåÝã2½Ï¡GÙEo0ö·Ù½Ïé…~Îx/ïB‹'‰üœhÔHLéz-èŤiŒ+N=1rnð2¢®Sœ-‚n§ÑÙÁ_}”7þ°‰W^y€W_}•§ÿáižýìY¸õª20*Þ›?nxúW¿*yæƒ>è+H•8p ùüÿÇ DM?äVœ8J÷xMwª]×@ ‚¦€ ‚`èÊãÐHIIåСCÜ}÷Ýlܸ‘W^yŸÏÇgŸ}ÆMÒMŸCüñm2àˆ÷fB®oû;ˆ¢È›o¾ÉO<(Š1ãsß¾},Z´ˆŽŽŽÁµcÇvìØÁرcc£µ‡Reß}êaÔåÊÌÿÍG¤–DïÖcqzóHVùSHš û¡æß!Ô ºÀĉ˜7ÏÂ[oÊ‚ ظq#¿øÅ/ذa’*Ú9~c AE*++yë­·¸é¦›ø§ü€å âôûù?o¼Á¨I“¸çî»yþé§©ii¡º±‘Ñ“'c²Z#™WãÇ£*J¬ šÖå ögs¤¦¦2cþm +*ÆœœÊˆñ¯Y\k^÷Àå ’õ m¨¦Æ_ß*Z¯$/„õkà&zG BÑ `Y4y䪾 ¢I2ó_oþ«õ_Ùs¨‚Ÿÿüç¼üòËÑžô‰õurrªâ&××—®V)ŠÛ$uçÎ|¿ß?>¨2º¬ôh–X4#<²R÷ŽT'n¿½n@ÃòÀgãTE)Ð4ízÔRyÂŒÛ;tÏÝŸÞ ¤\Ï|ž`½=ï„OýŸÊÀ\À8ÔE :öQâ=24V³¿êƒ sÁÕE]GáôߦrÉt;Õ)ßBJH­Ÿ—m<Í–ï’ä‡s«r¯e˜àñÄOû·Xúš±'á1à÷B?rFè-wèÓªlÕx]_=ÐÍ:9Bøtþ^çºÚœMQ¯pýra0û:ÀÑ䟮ûâ>i'¨²†ºè¤°ÞPF²8Hh]ëŸ6Wƒÿ-ÈZ±5T?4®ƒšƒ`橳ðëÆ[Aàwúó7?Þ§ÖF‹ß?EîŠå8O…0ÈïQgaPÞÂá^®“´Ú[È“_C­YŽÞQç_L|ñ'ñZ?ú~½ƒ?j|M,¾v`èzu¯G€e®ãPÿF>mÉgžq`ãrZ1~ß”ÿÝÿò‡ò¸o ÏùÒ¤µ„ÞÝ‚îÎþk\¹CÐù?Z=Ùº±ýn!+¹«kÌ5ÑÂpéÇ …A—Ðu‰ÝÅü°n1ç×ÿo Žpm-Þ={ø²¸ÍÎuëbj'éÖ[Q}møW"¾¤õ7¯‹é8ë¼%8BÈ_7ð|\yÒSˆ¿†0R%Æûzg3º7§Ïú­‘õEÉ‚%y!›<ŸàÑ\_è3ŸÁÓꉺ&Áöºt?‚qZ÷¾ƒÛ"€˜¨T¹ílí(ãÆ™œ ô¸ÎºÓ­]æ¤Î˜ŒDnÈJübàðíÙÃå•+c²ø‹ö¦¬]é²§#¶lÅ_w€úï¿€dëZ?é ®ÿÔÊÈâ”ü¹¯ÐQ¿QŠz+}TÉ7  nt]E4L•ÿf1¡ßoê Úª•è:˜Í©NªækKs !¢L¾ÀÍiQmïkº2þc¤Ô™hÚLtµ ]+E0éÌíMáŒ'JO\Eìw çT ÚWû¯Üp®ËêWùÛ¹yÜUôÅÀ! C¨»&›åêÚ²Œ`0!&}qPÄÖOêBž$€("’€(õ  ¹ìïó *º®`4¥ ^ŽtïKb\µˆÐºÐ;ó®ºÂ‘=¥$@ IL¢Y¼~#©÷ÍŒ'O¡qJW&¾bá\ϰÓîÌÑï;¦=¥èn5‘æp*áZBZBVZ”äAhä«7Á, ­&ÿ3%/ # #) - ( '# 5 ;=5 )#9&'3#"#,--)()4+('-7+36/5;856:3/1-IQO q J:T-b$f* s. r#k9}>m5D4.Z>.O5,n:!c4 n-4S'&U$iFBE5^Kd(*`V·7 ðàࡽº!ƒìq@„ Ãl«vã²ÃÌ­:YĪÓƒN¼¸ñŒl±cL° º*C<;UA!IQl›66NÒÏ Œþq«Äšê.nP`/äë«›œòMðÉ£~¢0Úºá€ØWSoèÀwÜ€h T̈AΘÒÔR_AL²IqçÝOP‡ …CÀ 2´Õ‘|ôaôÊ6«™v+4ÚPpû¨ãŽ<>%À-¦ÀÇA5C)ÔȼtA  K<ûä3Ê~8 ¥”8I !MâbÊ_TɲŽY6Ðu›\hæBì"Λ߈36—ÍâÍ8ß\sÄZŽœz ÄÁ1áz„@¯ÌyYsn–дhÃM7Ã,!@,ÜdZašé§Ò°ÆAjM¤1ú©‡Á|ôqÇþ}ÔT wôa+€{ðÁ‡6PB¾ÛG UÖÐk¬`.@G¯¾úf•â1ùЭèA8öèí·àê¤@3$c 3D 0¤ÐB0í@—€2¡° 0îèÀ½¡Ä¿þ&° (œjðL"õ‘ Ïe 2dp„ GX’fv ‰A¨8ßs %—½rM6w~shàŒs7o*Ÿ/Ë)sËË8$‹%^7ÀHšžÀÃ4Ó‚Ì6Ü@ 0qp €lãI @Ú c5òõÑ+¬zXÀ{ìÁ*uTiD«wÔìx°êjuü¶6e÷áGþPò;ßÖ²É1ae•ÐØ‰ƒ€îâŒ7^RÔ`B!P# (³HP8ãI÷bÔ¸ž%ËrÎãCA™3\¥…iÆ.û&Ì1D€1âl(€Š| !,“¾(|ø"dÓ]óãŠPapCÍÄ£7Û m®ˆnZ\™…v\÷AÇ zÐaDÁêZÀ }Ð'@hÛ,æ*Ùz¨0°=L òÑÃ_`€úˆ@¡jˆxÊli‰ӊã6ÈÁVdò°ƒžSü"ù@á Àà ÑU骈À½6÷Â@ Ôd± 2™kþ4¼Î$81‰"vœH ›n÷ J¨G²Ç7Æñ&ïŒÂf&<,ÎÉ]n¢ÄÈÑÅ„`J(øRM‹/»èFz 28L ò¡ƒ ÔG¿`­Êoô Z¥‡ºäóQOßüv,þ̓ 6ªQ†![€ÈK'x yð“  %A: Ëq™CER€y¬ð^—h # ™XVɪ€Œx0^|N4É !B`!NÔå6¿HðÔ†3d#w¸â2µxE.|\4ƒ7è¤3 {s÷ ˆ!*ˆùêƒÈõñ~`Uj°¶<€/þ(²è0D¶H ]³A Zƒ\•à‘y ÷²Wèå›–`#×-QZô¢K@0!ÉÆzì`”ÔX!:PA`N¶”Ìç2GC•úÏ è˜ƒ4|0!-Ü ©+é±ÂÌ­C 0F>~e°C / þW`“fôc¿)Èä’"^} ™Å›¼±MHa.;­ø'âRÑ;3Ìr7FÌQ ϧ2N hcÝ`†Ö‚ ¦E#ȘÊZ™vØp ?T( ¹ÆÓVq«_ðàµWé 6 –׸+_åmo\C(T²Ê65T¢ƒ â,Ká 'Õ€Gð…w죆 „Á(•c¶™3”–”{©x,&ˆôa$Ö ±É™D°€âìîñt I€eâH™‘W¨k$YÚIÀ3Ȱ!qAªÐ‹I¡ñ]·`7„™hޝ>±ØFþ{åKØôQXümU¬‚Â+BÖaZz]–€È­}|SpAØ ž&d–ÐĶnäI ;úÑaÃ= ܇ÀrB—FÔ¸*É¢_5¹"AD]CÒSˆBÚlt Q€xäp€tê„ ù!G* é^ûš)(ƒ>a†dΖÉtcX aR… Ã.6JŽWR d×(•"mDƒLKïGBÐeGàø&^Ô¤]^ÿúÝðþ Z(¢ìÔ[ ^À‡;±­èâ ì>‰µ*¼'*O=cÈx‘©nD I@nà¨A|ñˆ»ãÍñŽ/®ÒI¹Ìîþ$p›üä(O¹ÊWÎò–»üå0¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéPºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùоô§Oýê[ÿúØÏ¾ö·þÏýî{ÿûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿÿ€8€X€x€˜€ ¸€ Ø€ø€8Xx˜¸Øø ‚"8‚$X‚&x‚(˜‚*¸‚,Ø‚.ø‚0ƒ28ƒ4Xƒ6xƒ8˜ƒ:¸ƒ<؃>øƒ@„B8„DX„Fx„H˜„J¸„LØ„Nø„P…R8…TX…Vx…X˜…Z¸…\Ø…^ø…`†b8†dX†fx†h˜†j¸†l؆nø†p‡r8‡tX‡vx‡x˜‡z¸‡|؇~ø‡€ˆ‚8ˆ„Xˆ†xˆþˆ˜ˆŠ¸ˆŒØˆŽøˆ‰’8‰”X‰–x‰˜˜‰š¸‰œØ‰žø‰ Š¢8ФXЦxЍ˜Šª¸Š¬ØŠ®øŠ°‹²8‹´X‹¶x‹¸˜‹º¸‹¼Ø‹¾ø‹ÀŒÂ8ŒÄXŒÆxŒÈ˜ŒÊ¸ŒÌØŒÎøŒÐÒ8ÔXÖxØ˜Ú¸ÜØÞøàŽâ8ŽäXŽæxŽè˜Žê¸ŽìØŽîøŽðò8ôXöxø˜ú¸üØþø9Yy™ ¹ Ùù‘9‘Y‘y‘™‘¹‘Ù‘ù‘ ’"9’$Y’&y’(™’*¹’,Ù’.ù’0ü“29“4Y“6y“8™“:¹“<Ù“>ù“@”B9”DY”Fy”H™”J¹”LÙ”Nù”P•R9•TY•Vy•X™•Z¹•\Ù•^ù•`–b9–dY–fy–h™–j¹–lÙ–nù–p—r9—tY—vy—x™—z¹—|Ù—~ù—€˜‚9˜„Y˜†y˜ˆ™˜Š¹˜ŒÙ˜Žù˜™’9™”Y™s ˜—˜ š™™p¹™É™Ÿé™o š£)š¥Išniš©‰š«©šmÉš¯éš± ›l)›µI›·i›k‰›»©›½É›jé›Á œÃ)œiIœÇiœÉ‰œh©œÍÉœÏéœg Ó !ù.þ!þmatt@eden: ~/tappy,ý!ùþ!þmatt@eden: ~/tappy,o8pŸÁƒ *DÈP!A†„hP¢@Š-bÜ@€ÄÔÄãç‘‹(;D 1v4lTy)€)óþÈL© “ÍÒ˜©lÑÏ ;)Ò<*”hMª J q)Ô C1Z¤ÚP£V¯!ùþ!þmatt@eden: ~/tappy,!x8pŸÁƒ *DÈP!A†„hP¢@Š-bÜgÀF ÔÄÛ— GØj×äE0w:PR”"oÐIÎ<Ø(Æ]Ž“n©Š°ŒOAUAØHÓ&œ:7ªtçä0˜>†´ç”I™ ¸ˆ±ãG!ùþ!þmatt@eden: ~/tappy,*x8pŸÁƒ *DÈP!A†„hP¢@Š-bÜgÀF~Ùó—OTÜZ$…‹$PRÀëT……LæÎÑsH€EؽO'?¤2OLˆÐ(a±"ͽ?O PÏŸ?sNb*•8ÖaYœ!ùþ!þmatt@eden: ~/tappy,2 eýH À‚"¨paC„.4@€™wþÌ1 ÀŸZ욬XsïRèùà(€—§Ž`îù›9ÓLwCèlà` y„8"`+_# .Ü\èXñ¢¿}¤h08ubD‚!ù*þ!þmatt@eden: ~/tappy,;- þý Là‚Àƒ*D¸ à‡]ÐåxHqaĉ^ÌÈÑßFŽªí9RQ̼ÛgŽI~ÙÛ—OTž‘,y2æÌš`0Ý ¥Å®ÉŠ5÷þ½µ.H I^-z4éÔª+°xéQ"B)ô|œÀË“¶¦*X4{î̓~æÞÎ}¦òNÖÎÑ“÷ \ø°?ÄŠÁ¸ëŠÔ°»÷I®^ºhÖ÷鮿*õvœ•GñBSæ º|:õCTd Aí-‡Øj×(Æ 7P¢QÂbEš¨´yû@Àr)žÿ©ûe¥;CkUŽ$E¥x#Í9!‹²û¾ïÕϧ_!ùa!þmatt@eden: ~/tappy,h !ùþ!þmatt@eden: ~/tappy,r EýùKà‚Àƒþº ËP B†9TÛG‘¢¢ŒA7¤Aƒ.l˜pdÇ$«ÔÛA2µO@´Ü PÀ—wûÜ !ùþ!þmatt@eden: ~/tappy,{Dý@¶oß9$ü%€Á¢E®v9üI”Øš ~ÙË×oß ÊPI…Þ Ô%äPmf¨„4é„0{Íõ!ù"þ!þmatt@eden: ~/tappy,ƒ Uý Xoß¾Q8øK ìTŒ,ÀÜéÀS šy ÀpÑE·TPÀìÒG["(󈑙'ŠÊØ)y¬Ý*Í<‡ä¢J“þ‚eTèBeEƒ!ùþ!þmatt@eden: ~/tappy,Œ $øH°A‚Œ ÐŸA®¼¡` ‡gù‹]€!ù$þ!þmatt@eden: ~/tappy,– ?ýùKà‚Àƒ]Ðå8(0áBªí›8Q‘cÐ iÐà@C… ý9 9’a•z;B† ö ˆ–|y·Ï¡€!ùþ!þmatt@eden: ~/tappy,ž Eø`æÝ>sL@‹]“kîýñ'…žxyòFß¾Oqt7¤É(Ê#$ÐŸË ó5âÂÍŒ?’¢!ù þ!þmatt@eden: ~/tappy,§~ý HpŸÁƒ *ô‡°á‚ >QâÄŠ'RĨ‘!G~ÙÛ—OT¸µ.ÈŠI  ŒX@/SbÜ'Áßhíݰ±"Q°»÷éäN…¦Ì4Ó¡4JV¬HsïOU„HÕÀ3hÎI¯>Õ¸ö"Æ€!ùþ!þmatt@eden: ~/tappy,°{ýHྃ ,X0¡Ã… :L1âD…¸€Ððâ¾…]Ðåèx¤H’ýq¨æ¯eKE<~À` º! ˆéÑäHƒ=†ü)1(€¡(¬RoGÒ„† ö ˆ– Æ Ò Á–G‚œ(€Ç]ê,Ù@•z;ˆF‚Ú' Znàì)àË;í M…(1hήBÁn !ù þ!þmatt@eden: ~/tappy,Ô‚ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%n @€™wþÌ1!ù±$-vMV¬¹÷GÀGRèù )€—'7ÁÜóG”¨) ýq7¤Ó^Ä)Ɇ#ÐÊ׈ 7¤NäyÒß>R4Ä> ©–"Û›o7!ùþ!þmatt@eden: ~/tappy,݃ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%n¸á—=ùDEð€€[났h‘ÄËð2UÁãF ¦@kçèF‘ºÄ"ìÞ§•;N™'èæEÐ(Y±"ͽ?V'bUÏŸ?sN „}r-ŶQán !ùþ!þmatt@eden: ~/tappy,æ{ýHྃ ,X0¡Ã… :L1âD…¸€Ððâ¾…]Ðåèx¤H’ýq¨æ¯eKE<~À` º! ˆéÑäHƒ=†ü)1(€¡(¬RoGÒ„† ö ˆ–8,¶Ìé/Á{ûòë!ù þ!þmatt@eden: ~/tappy,  EýI™·oߥþ h€¢Ù"„ &Pv0!‰!^ô— &ˆ;>´ˆ±¢¿MB°Ì“F…·Ø5Érc£¿Èöµ3!ù þ!þmatt@eden: ~/tappy, Eýù`æÝ>sL ¤Å®ÉŠ5÷þø“BχB¼<ù£oŸG§6ºÒ dåR(Ð_´ò5âÂÍ…‹=’¢!ù>þ!þmatt@eden: ~/tappy, LýùÛðËÞ¾|¢(pk].’( €×©Jð7Z;G7ø Paú>aÌ8rÊsN !ùþ!þnosetests tap.tests.test_rules,ý!ùþ!þnosetests tap.tests.test_rules,\ySÁ‡gùË]!ùþ!þnosetests tap.tests.test_rules, \ySÁ‡gùË]!ùþ!þnosetests tap.tests.test_rules,<Ÿý H°`AWÞT(( !† l¨ð`Dƒ1rx–ïP‚8z)òãÀ ¢<”±å@¹Üõ01»™ÄÄùr'CŸ.ƒ J´¨Ñ£H“*%º¯©S§K£f|JUªU‚TŸ^ÝšêV«]›~v¬Ô°ûÌFE«v)Û¶IßÂ=*wnѺv‡âÍt/ß–~ÿb ,¸ áÂ#ö!ùþ!þmatt@eden: ~/tappy,%uEþýH ¿ƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“ ªDɲ¥Ë—0cÊœI³¦Í›8_ª,˜³§ÏŸ@ƒ J´¨Ñ; ]Ê´©Ó§P£J™tàÔ«X³jÝʵ+̪¼ŠK¶¬Ù³GÁ¢]˶­Û·p!ªK·®Ý»x‹ÎÍË·¯ß¿€50 p«þNXx1BÅÂ"l< @BÈ’R¶<¹qæÄž/7æ|2i”?›]˜ëÈŒCwv¬YvmÚ  ÞüúôêÇ£_»®;6îÔ '^9¹íÜÍ™«~ŽÙ8ìÙÑK/×~œzãáܯþ‡÷í½{qéÎÍgGýœ7zìäqÿ¾-:xèÓå—_üýAåêÖ}ñÕçÞx€ËAíè`•¸ì³OƒE8¡JHain˜X‡r¨aˆŽ˜á„;œè!„&Šˆâ‰îÈâ…*Êb…ùÐèbŒ5öèâŠ7þhc‹%îÃã) ãF2 $‘3>©c‘RVÉä‘T:©¥T&Y$–Qz¥“b™%ŽSΦ™c.Ée›JÆÙ¥›H¾Ùa™P²‰KŽR®™çŸMö8Ð*j€Uj(B‰ªÂŒ*º‘:jÀcŠª@éA.Ê©¢,\ú©¥•z*P¦¢žJꨦvʦþªªj©³²ªBªjZê ¸‚Ú몱êj믭úZê­Ç+l°ÊÂꪲIJ)«¯Ò:m¬µ2{¬³¨Òú+ Èkm²ä²Êë¸â¦,·…žK-´Û–‹í·ájKíµ¹Âkn´øv«n®ìªàî¼òœ`R%¬ð ߵWÃG,ñÄQ=LñÅg¬qLoìñÇ ‡LQÇ"—lòÉ“ŒòÊ,·ìòË0Ç,óÌ4×lóÍ8ç¬óÎ<÷ìóÏ@C´€ °j4tÑÐE=‚ôÓP34ò# ŸuÄA5vmŠuN‰MöFȲÏ"§]$ðì3 Y/ôvû˜ƒ„e¬ñ¤q9CärJmݵ„Š oÆ;Å3Q€ì¿Ø³O>¢PoýõÙû³Aøã—ÿÐî9^BZj,¿OóϤA3lû35ƒÈŸžÑ¿PƒPÿ¾'z´Ežq;ð‰|ÔËM&yôÎDX´àJ€2N„,ƒ4¼€Ž8Í!`ÀÐ1„4àþ-ØÑ„¬áâ-Ö„´ ßËá{øC%®£ )x¢çìÆ4C~Q€2B-¼0†Ùd±Ž,ÃßkÈîþð sá˜cì˜ 8&¬ –ØÄ'ÆqƒˆüØáì€ä‚¦Kˆ‚q ¼«ÛB° Pò!™CH}€<^ÀQ¼0EÊÒ¤2ƒI@2ÀxKZ“ItHêñZäã¼Å²´Ü¢A¸…v©ƒ[Ä(™©ÚŸ S¹ÊDZd»ëÚ:ôæü"üè‡>hËnJcšTÈ.}NÌm!`¸ñ¼6ÀµÃ7þ8M+8wà3 ‘%-ý!F0vàœù+¦€Jaê²;XhC °Ëˆ2š $iúùÏkzc"@r D¤B °à@”ó „È U˰½ó `p}èÎ ¸Ç'`¹ÏÒU¨5mˆ@ç§ ƒ"Ô!˜ddbLùI…LAv@Þ  FäõñÒôÍQ‡úѲFl‘Ø@3ÒgÀ˜vàälªe`ÚNý­  ©hB:ˆÀÓQa‚àŒ^€¿V¦_EÈK«N‡ì¯" gp…7TÓµŒ¤A¨µ#˜!2Ãf$↠),`ñjÖÖÚ¥ƒIþ s€0*Å‚<âjPÆâ—u•¤4>QÆ| Àùh\à†U J`Á Ò€\ú#Ã-nžÝé"1¸BtG´Ï—>µ!(Cè´`ØÉî¶ó©záÆ™4mí1X—˜´@˜¨"w¥K]ëºöÀ}9\.UèGUBçèKåjΘ> 7ˆ¾·vB{Ü»_ “·â9á枇k[? ™ãÄé0&„ˆÚR¦}›øæ–™è¹£=fÃŽ¹©ã¾¤ µëZö ¼™x˜޲”§Lå*[ùÊXβ–·Ìå.{ùË`³˜ÇLæ2›ùÌhN³þš×Ìæ6»ùÍp޳œçLç:ÛùÎxγž÷Ìç>ûùÏ€´ MèBúЈN´¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéN{ºÍ Xƒøðç<£¯o”?ÍêÀDˆ'ü%6@A(`%mµ®ö×wÒo=ÄqÙÁÍ]Û/$95‹uXÀÔ‡¨E;~Üc[Û-B\¢l˜ºûøàµÇ}Èbà6 ß:Šw”®ÚäŽ÷XÒæÓ `ì°­ áf "’òøZzmNªÚÁ3NqY;œ,Ñcâ ÝAmS÷õª‹¨êÃ7¾•PÛCçpžú (ˆ<Бˆã(¿J@!ùþ!þmatt@eden: ~/tappy,oþý H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å…@(ÀR¦7üêà¢I((˜\ɲ¥I_–”tÙ2„3a¥‰€J<%ÔÀlŽœ \l¤¹PAÑ LøA@Ô–fq{  r2TЋ²¥. ˆ 6¢€0Û¤ÝèúpJ«N$±ÂËÑÖ:b» ËqUaˆh¦*( ) ݇Œ‰“Ô¶0EƸU:0À.nP*#à‹›´-œà“§€E^´u;úð%ž{k ¤2ïO[Á„-#œ2íÐcƒ&mŠ":aäÉÍ…?@e ‹ã>C9îü•YÔ©Mþ°výîÙé;Y±²3SÀ-Sœr¨¶¯~}E]I@Ó·ï’®èO}£à €ûäS`I#ÈC€¼„²°cABH²ÉrÑ”8 ‚86då8ã\sDWˆ*ZÕÁ1'Š£bI¯Œ8€?`Œˆ€BÔ¢ 7fÝ @,DrCÍRG&™¤4KuÐK7HX%f³ÍwðÑÇ{èQGW%té%ä `Ã|ðᇠþ”Ðæœ^ê&€löQgÐÁf›~D0PdÜ<ÁB´Ô@ 4cœ‡ ŒÎ 4p@I À°B ÁôCXÊ„ƒÀ¸£ ž2*0íœ*À2þ àÏÏàGÐÓü1Ð!ÃGôjɆQÀb3ŽÇ\CÉþÀrÍ5Ù,{„?!¼xM8â|s"€óÍ5ÞŒã-¸âfÀ,ãP™AÌN0Ìp# g°Í˼Àı\fÉ*PÛ #(GâéÁæ}è¡Û{ìqG|Œ€^ZÌœ àÑå—uÔ1; ± ­W%ä1´ÀmêÁ)P¹ \B!P#ªÊ,¢3žªÑþ•´‚ÄÛb)àŒ")·áÖ\o‚ƒCÏQæÕH&I¤KÚ7$†ûMU¾ˆC¢ÕJB€ÙV†þ’]U$Ÿ?Ùý]нD³Äc°€Vs&ôAÇj“÷¡³?p‚,P„ž]@—ˬ=F±Ä`æÉ Ë€ÇI¡ 2ƒc¨+ŠjB—ò°gÐÏuðK<ù 8Ñ—x‹*€Š‰òªl$=C˲Ê^-³G’L2 'Þƒ¯5'*5¶±ã8ìC—íÛþp‘b¤ëO>F;¢äÀH¢™ß¶#.Äep…#àBšœ¸Lr›£ƒ,.µ cy€^&9 Xiƒ¥ˆœèT'ÒÅì "І4ʧ @=ƒHÒi íwƒ!HŠ„`þƒ¨’G¸å5D€-˜G8^ÄŠI+H´§äl‚P›ÙÐç*˜« XȆ8Üõ¾ øƒ æB@ï7?Œ×8ƒ7H”Ê¡œ‰‘ÁB@;yS,¶Ñˆ¼©‚äÐ¥:Ô ! @ üt¦’˜ptâ±\ 6i؉ç„ÁìÀsl‚' ÀmÑ•)zâPU¨Ç´ úc>AbWœ’´¥í’Qô‡Ð1jøæpÕDB ,B„q£€#ýqŒ Aº<3¢”ñCt|Î7¾!¶„ì 'ð@YH…¸@E%)œdU"ùLiqþ1`% æÇÈÑ!}«±Áé°'§ v„›`ûÉ×Ibtˆ§kPКDŸ=HGöÂA{„« ´OA sQ€2P!NaG^Œ|ü€h2¥i;nz%fôc*È0ƒsx/‹a—8¼1GLA\×8·ì¦ÍnˆœÈ–U—UNùÙÈŒ ÁŒ“8”ÌT#´H㸠´lš©Yd#ƒòŸ~Üœ Ð%1u)ƒÀCÄÀ䦙, ˆ S PvÁ•µì“åÈ.º¡Àeº‚ŒhNª‰ŒÂäûh‡!\CaØ#AæèATjŽù§•ÊhþmþcW1è#$¬øid€‹ÁÌ7@D¢ˆ!ºÆÙÞwUIÈèEåÂê ªE‰-dløEÖ9:* ©ó¼Ò-àÚ a”OÜhÂc8 ƒ‘¯8€‡ ‚îNfÒS|îô'?Ø©ù¨ #¿mT© I€%4Q;‚@ ö(p!¢K)*ãyÃQQ9" SuH:ز-Ãá¿‚„€ã¸FÀ¢!!·&kxA<TaH2a‰Êp@ª!Ö¡2ˆHø°VØaC>ña‡€Ä`È5‹k,«º ‘]4ˆ!>FEJ¦9%bÀnà35)¹Ì=¹ã<îxîxoý‘]QÜZ$…‹$ð:UÁŸE‹S ¹st#ÀÅX„éûá¢?Sæ €F ‹iîý Þ>}æœ!ùþ!þmatt@eden: ~/tappy,2m|ý HpŸÁƒ *ô‡°á‚ >QâÄŠ'RĨ‘!G̼ÛgŽIAÒb×dÅ~>PFô'…ž y’é̽†¦x"ôÆÝH8z€y„pB¬@€­|b¸psiF" ’¢áu_GŒf5¢=‹1 !ù;þ!þmatt@eden: ~/tappy,;m7þýH ¿ƒ*\Ȱᾇ! ,X°¡Å‹#jœHq Æ 5FäØ¤ÉŒ"NLàBÅ“ ´ ™rßÄ.èr¼„y°Kº4SÞ̹“§O kÚäPM¡¢þ°·Ï“7ü²·/Ÿ¨þ:<ÓøÔŸV®^Á* €ÁtC48À–»&+ÖÜûÃöÖº&)\$ÁÚömܹ}ï¦h1ØŸR«%8‚žQyy* €—)° q"=Øù³B¥Š.FŸFSüMÖÎÑ ¨E+œ]ûöA¥TOÆÝa¹p À"ìÞ'Ðþt+\.LßsÇ5'V©·£ y„pw3@Ež ÜÛw4$?Ov¡CPûDËm¶ò5âÂÍ…¨h(Á iì…™?òÑg_8`øüñžH ðÅ;þ´cT˜ñN>ûBCTjPU•X‘vá>2XâCæ 8áFÄÓJ‘DÑ8fW£G<¤T@!ùþ!þmatt@eden: ~/tappy,hmHýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%nìØpdHIj¼¨R$Ë“O¢Ü¦É)m¾Äy3ãÇ€!ùþ!þmatt@eden: ~/tappy,rmO8pŸÁƒ *DÈP!A†„hP¢@Š-bÜgÀÆŽ5b(`€ÉG ÀåÏ_;)) ü ¢¦˜AŽ)“gNŸ Fz0 !ù/þ!þmatt@eden: ~/tappy,{mO8pŸÁƒ *DÈP!A†„hP¢@Š-bÜgÀÆŽ5b(`€ÉG ÀåÏ_;)) ü ¢¦˜AŽ)“gNŸ Fz0 !ù þ!þmatt@eden: ~/tappy,ƒmŽýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%nÀËS‚"èíø(€V(˜¢ŽÆG¯TQxÅn—u>úóÂî¯}Ê”‚ TŠº"ÈŠAª…)€PÏþ-²¢Ú!«ý%X–ÏI}>mi /éf¨ Ë0€Æ‹Ò ù1 !ù þ!þmatt@eden: ~/tappy,mu8pŸÁƒ *DÈP!A†„hP —z‚RÜ'PÖ¾E&R(„¥ÈóçïRH÷ `€¢™K‘I&» s£ÀÊxˆù3èË¡>Xæ©Î†Jiµk¢åÒ‘;Û×ÎÐÕœ¿B•H4lÌ€!ùþ!þmatt@eden: ~/tappy,•m{ýHྃ ,X0¡Ã… :L1âD…¸€Ððâ¾…]Ðåèx¤H’ýq¨æ¯eKE<~À` º! ˆéÑäHƒ=†ü)1(€¡(¬RoGÒ„† ö ˆ–QâÄŠ'RĨ‘!ÆþF 3h HŒûø[cE‹_írx¬¸05A3# ìð+^¾xûåt˜@ª )°ÐŠÒŸÍA ;TcúQ3TB’.Š$ a÷HöàjñaSg/b !ùþ!þmatt@eden: ~/tappy,øm‹ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%nô'@W<ùFáyÑ_e¡bhÆN€^^ @C3O77ÐÉSÀ-UA/ HÀ SÑ£I'U¶¨ÀRe@?N]§F°|?¢:  @;xþÎ!áùÑ_ÙE<Ž$êqdÙu/!ùþ!þmatt@eden: ~/tappy,m_ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%nìØpcH‘ 0 e”#€ËŸ?w:`:” àƒŠŸt&TòãIIj¼¨T(Æ¢#O~ !ùþ!þmatt@eden: ~/tappy, mx8pŸÁƒ *DÈP!A†„hP¢@Š-bÜgÀF~Ùó—OTÜZ$…‹$PRÀëT……LæÎÑsH€EؽO'?¤2OLˆÐ(a±"ͽ?O PÏŸ?sNb*•8ÖaYœ!ùþ!þmatt@eden: ~/tappy,m{ýHྃ ,X0¡Ã… :L1âD…¸€Ððâ¾…]Ðåèx¤H’ýq¨æ¯eKE<~À` º! ˆéÑäHƒ=†ü)1(€¡(¬RoGÒ„† ö ˆ–'P° UXè!ý诵A*9T›:R2TB JMú a÷üå3×CÕd)Â}òc@!ù&þ!þmatt@eden: ~/tappy,7m€ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ©ñ¢?*Ðüfï\ý‰a'$» ¹Lø@̺aÖY#ôc€™fegg˜UˆU×tcÏŸÁ05êsBÖ­V}^Ⱥ®BÌŒ7vl¨-€!ù!þmatt@eden: ~/tappy,Am H° Áƒ*\Ȱ¡Ã‡#& !ùþ!þmatt@eden: ~/tappy,Jq EýùKà‚Àƒþº ËP B†9TÛG‘¢¢ŒA7¤Aƒ.l˜pdÇ$«ÔÛA2µO@´Ü PÀ—wûÜ !ùþ!þmatt@eden: ~/tappy,StDý@¶oß9$ü%€Á¢E®v9üI”Øš ~ÙË×oß ÊPI…Þ Ô%äPmf¨„4é„0{Íõ!ùþ!þmatt@eden: ~/tappy,[t Uý Xoß¾Q8øK ìTŒ,ÀÜéÀS šy ÀpÑE·TPÀìÒG["(󈑙'ŠÊØ)y¬Ý*Í<‡ä¢J“þ‚eTèBeEƒ!ù þ!þmatt@eden: ~/tappy,du$øH°A‚Œ ÐŸA®¼¡` ‡gù‹]€!ùþ!þmatt@eden: ~/tappy,nq ?ýùKà‚Àƒ]Ðå8(0áBªí›8Q‘cÐ iÐà@C… ý9 9’a•z;B† ö ˆ–|y·Ï¡€!ùþ!þmatt@eden: ~/tappy,vt Eø`æÝ>sL@‹]“kîýñ'…žxyòFß¾Oqt7¤É(Ê#$ÐŸË ó5âÂÍŒ?’¢!ùþ!þmatt@eden: ~/tappy,t LýùÛðËÞ¾|¢(pk].’( €×©Jð7Z;G7ø Paú>aÌ8rÊsN !ù þ!þmatt@eden: ~/tappy,ˆmwý HpŸÁƒ *ô‡°á‚ >Q¢¿. @¬8° º#vüÒ!‡j 1¬¸ÏŸcÐ iÐàÀJŽ=‚ȲeN’Ñrã¦H¾¼Û×ÎU‡¿" +ÖâÞ!ù"þ!þmatt@eden: ~/tappy,‘mƒýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%n `Ã/{þò‰Šà#·ÖYÑ"‰–ý àeª‚Çœ¦@kçèIœ À"ìÞ'–;N™'éÄÐ(Y±"ͽ?VbUÏŸ?sN „¥r-ÆQÛ~ !ùþ!þmatt@eden: ~/tappy,šmWýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%nìØpdHIj¼¨R$Ë“ ^ySaåÄ9<ówÈæÃr¹ëá“"L“!S½!ùþ!þmatt@eden: ~/tappy,¤mq8pŸÁƒ *DÈP!A†„hP`RÜ'° º)vü"‡jþR¦T`ã> Æ Ò Á–G‚œ(€Ç]ê,Ù@•z;ˆF‚Ú' Znàì)àË;í M…(1hήBÁn !ùþ!þmatt@eden: ~/tappy,¬m‚ýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%n @€™wþÌ1!ù±$-vMV¬¹÷GÀGRèù )€—'7ÁÜóG”¨) ýq7¤Ó^Ä)Ɇ#ÐÊ׈ 7¤NäyÒß>R4Ä> ©–"Û›o7!ùþ!þmatt@eden: ~/tappy,µmƒýHྃ ,X0¡Ã… :L1âD… ^ÜW‘áFŽ%n¸á—=ùDEð€€[났h‘ÄËð2UÁãF ¦@kçèF‘ºÄ"ìÞ§•;N™'èæEÐ(Y±"ͽ?V'bUÏŸ?sN „}r-ŶQán !ùþ!þmatt@eden: ~/tappy,¾m{ýHྃ ,X0¡Ã… :L1âD…¸€Ððâ¾…]Ðåèx¤H’ýq¨æ¯eKE<~À` º! ˆéÑäHƒ=†ü)1(€¡(¬RoGÒ„† ö ˆ–Qü ¸µ.H I (äuª‚¿‹§@sçèFŒþ`¦ïSB¦Ì@%,V¤¹÷€5ðöé3çDb@!ù!þ6nosetests --with-tap --tap-stream tap.tests.test_rules,-‹!ùþ!þ6nosetests --with-tap --tap-stream tap.tests.test_rules,•ò þý H° Áƒ*\È¡yûö- PP  ¸­_»Q32й0ÁE’Fœˆ’àj;8D¡˜ˆ8÷­Š PÀ²}j&È¥/ß¾sOj¶\ZPÍ(Ø%]¦_~ââF¬_匸jÂÒ.è® -b>Q4(ÐꥩLŬª ¥žZ„ 4¨ÅîFŠ5©ÔÊ€ÀÉ@­h±æM°˜!_R*í_¦ž1LÐ á‚9 U^¡ÉÑØ…,™lóÒ[c÷¹û|0À+v ¨êŠ:µ’a¢(€Wc…âÀã·¯] ¹ ̼ÛgŽIÍ ¿ìþí{ËSA³DCL$T™mªUD¼xò ãD1ñ¤àð]ïèSTP ùAÙm×ÝwáE$ OúåDßòü!×`}þ¨ÇÞcµq&€ðpç@ö7á0³ˆ¸„‚j ð[piÐ 8CHËØÖÜs Q1Ï#@¨ðÂþ@ ;M¬°? À-챂 ILõ!E"¬'—?š)%c 3„`°å:^¶„c ­Ùæ› ¥-í4ñ0íè —’Äà$”í8¶¬See0g•`N¥§›p€a¥þˆ {>¢â4בø^”SNzO¥þÒ™B¦þtðL–Ë€¨‘,ëð(PêÌÀ,¦H€fmðÅ<‡àHb;¤D†QòâI²¼œRAEÍ€‚<»jgi Ç‹)ŒfÄš@RÌs™L×úíH¼Òë@RÐãƒ\Í]Ûœ¶­ëÏCv°A.²ùcž©ê rfšøêË/ `kʶMq\|#6º`O´ÄKÅqh.cTuà™d=ц9ëÏдãÈ 5ià ÃÔt 1AÏ\óÍÎ$0»õt‹)<Í+R½ûóòX2Ól3Î@—ÛNëèÎÞ®'T‰M³±Vín9å“´BPÏöµ3c ¢å2 ¤àþ^ 9]¶;| †@M`!Œ>Ÿ”çLØ€Q›ž®,dˆh§È2ÌKñÚzû©=¸póÄÐÅ*ôœ¡.®N°J½?Yþ–^9þby‹ç+¦÷™¬÷Ê RÔrw#…îG‹˜W(EfëÜ…—2—}4=‰ •öì“ ÅŒQ\ÁßrÐ2JÏJ<ãÚ>Š$$€ðìcÙmðKƒ·EàÕŒXâC  ¨"‹u<úìÓN %ù?ûäHB ¬‘âƒMið#’J&ä"Œ¢Èˆãñ8ˆƒ‰jÄ$)D|¨2æ¤x‘M>ú¬™ÐiI%WɨÛ-¦ÇA®ÝH2Q™ƒ„‚u¹Ï(_>ô§&긢GW&™¥?ÚÚ•Æè¨¦QÝxbŠ“ Ôé¥ZÝS IäþÍ(çÓÒqåH*”¾h©–=ÆŽøÎ޽b)cª›0‚<àù3Å<à)kB!ÔzkžGºc‹¾zZ, `‹;M¼L;:—……p4óK00:C4ИËN+¬qxܲN+´„é˯¿˜Y-|pITÌóH *¼p@¼‚ #›ëŽ/Ã.ǃ,²F 3ìpv ™ÛËÀ¸£ ( ZXà 8*³Ž0“Ï%ýÄB ¿¸CX¾•é‰+DÖÞ[Äý6P±V04üK;„ÕJ Yôü³n`S0ÆüG /œÂþÍïvÅy±÷Ãåë·Ü×}0Þ57L¸á‹mñ@%Ë<²Î$RR¶€x„çe¬‚Á>0¾°ãïõÊ:Ä ºâ7Î7äð” ‰ Ï’\ëO¶K ´õë߸ã8 ôìO ’µž°èt4Qï^C$ÀÜãçÏhRÐãCIð}ù¦Ä¶Ðz…?¾FæCLE=;,4ý¡åüŒÎDïÏý )ß)*0‘æ•„.Ñ#ލ59¢ˆIÊðAD AlKkíñ^¨q·õ¥ã0 Ù  @ÌI0?ÏqŠøLÄ P}\_ ágŠN|òx_ùÎÇ‹2„}þ”£ŸBz'+Î]'#ùZøÂÚP"X€üñ”ÒÁ‡§ Àgøðl šñ‡µŠ¨-á=°}9\¢ øD…-`°š@p UD hQ Þõ:„à"*ì’ÈhÀ$טBÒ‚F;q ˆþ’ ¹S € Áä@Ä Gš™BFšH_ÜÁHîÄñ0 ³#qB ý5„8©”Q^”~ðÕŸ“KÐôÔgÙÁL¥™e<òQ} Â]gL¥p¶ÂHR2*‡L$)op”Òôe‘Ôä Áp'‘جæ J1úΈÂãÖ@ÀEs”¥„â+†µÎgîã“àþŒç#gø¬Ö¬ ‚ަ"ƒtÇà¯yDXÆ=>,HÆ1lþºÏÿ„˜Iš®³“Êœ)þ´“Ò•‹!¥™*ÏØV.–u”@˜ ÁaA¸dèÔ‘¢ "B ÍRWáTz8¯&˜hL,AºM‘–´h JUÊÒ}6Sx=m+Y›ùÒ³"à¤)ÕÇ'lIºÑ7Ú ­Òy™—’Ô_wu]\µ¨:z ¤®%•©FRʽ>òƒ¡S! U¨OÎŒî+Ff¸²ˆÅ+[Û˜†6/! Mþ iK vèÀ{ØÃ*K¼³S*ʃËá‹o§¼ø=ªÐ€°çŸ BH¼zÒ[ ëÍ•fGÞÜ@kŠô¥†›©ùèHOú«JK—Ó &Œê#{;5ãæì7¤¬ÌgNóš³¼e¼á:Ðk6r>`ÍkLghÎa¾5 uÝ”bÙ!å|L>àÀ‚-̃H˜±mÑŽ/×Y#¿Nƒ g$"XGѱ±…—Álkp/{Ü”uçuÊ€A†Ú[ ‡—.ËÙÛbvw®Õž.`VµH)*Æ3VEmÌj7›…¯/«ш޵R MjH‘Šþœ€3X\H×ÍÆ“äq’aXÆIƒ¼ä<‡¼P$ÇË´pRÑ®´Ž.Ũ6 Iü0TV: !!'!@†>òñtˆ„àa àÉ1žFæ IMÌÑ%9$î u·žqcE¥å ¹9r>G¶ãíæú˜{ÉÑ®ö‚Ø]ãOÅÇñ®w¿Ë)oÈt›£s°Z $h:â#Åq¸ß<…wßÃó˜Ä µéøàS”wºkÄâúàú~¢ÕZÿɼAû8GßýAkìcò€;Òd.wÓe øht†Oüâ_%ø‚=â•‘&ÿùоô ' >Oÿùø@ ¶_Ï}+wzø×é>÷¯<‘Šûä/¾Ê @è¶"Aûùõ¥þó§þø?‰Ø0$Gæø!2"QdÑ+¸u€°¦~(02±5ÿW|(€ 8HÁ+!ùþ!þ6nosetests --with-tap --tap-stream tap.tests.test_rules,¸Zþý 8P€.C*\Ȱ¡Ã‡#JœH±¢Å‹3jÜÈ£‚6:Hܬ‡ÈŽ(Sª\ɲ¥Ë— 8 1²E a^LàBGž>u ¥(EÞ¾}—rôgÏ¡!¦RêP³U4Nú[ðô%× tÝØ”cY–RæMºtã×E×R½hUÕ ¦ÊØJ0™¾}£phUÙ]ŽŽ…·]ÌP@Íô2n˜±a¨ÓÍ](À»¬+¿½‘´FÓQ§t Y²J1Gcï[5!õå! Í' ´CÖÍpr í؆6]?T`Í-ÑVÝe©úbõÉ‹,SŽ}àõŠßUþF4˜!•yʇç¸^bûˆï!ÆÇ˜ ™p– @¤¨åîF ›Mdšó’B̬3ÝC6a ESГ- †=û¤‚ƒþ$ÀLR\Qàƒ“èì°ÒÏȶ" `<û˜ÃDm𠆼EàÕ¸#ƒ3Öx£@ÄÏ_í"R’ðð³O>šu¸ž¤’RR™Ž<ŠâãoºÄ3e`JÕç Èue>©QžB ¨Ï_©ôSWÒ‰CB@ I¢?lµ uPÍ_± ù[‘6&”çžûœCDN ìØO9ú÷·˜2¦@)Û‰Ö§?þ™ ˜`¥j(¬”2‘˜SŠéϪFúå/föXk°G )#•æ¸c¯>Úºê¡p¹O“(ÐL" wRrT%ë®Ï ²°cA2¾c$¹aúØA‹ÓŽ OzþL^¶Ûv+շɸ&Ìùk¹¾$‹‚°ÚâN/ÓŽ€+b!‡€Ÿ­$l 3D @gì4±Â÷¤Gê:A¬ÐBÀÊÀ"“lòo´¤¼rË"÷H *¼p@Bǰ‚ GwæŽ/3qÐó ]tÓ·À,s8:dS(1dŒ;! ¤&U À s0î¦Ý)ϳ Ë)JÜìÜÕYþ;PÒÇ8\ò) 6 lx΃ÿæ™Ê,ûÀ×ÏYð¼¤üÂ/úTYU3‡Pµ¸à;§-ó/í¸mwØ~.Ðè:ŸÜ3ä-ðr)ÌŒ#샓šrî4z3ã&£Lû¶góÌ5ó¾³CI_ý£3‰ˆ4ÞŸ+%Àì*Dž¼ÖÌGÀ+ë¬ûúì?»¬üÒ\Û<†;¤Pòþˆ fôëOTžƒ[ú,CÞí¶ÖµØ+=EDB´å‰Ød!{BÆ2j„."€'èÓH> æˆ9SÔf!ïù`aÅ OÔŒ õP‘B`(äEÓk ?h¨Ì¢þY@^Õ v({ 5ÿé _È¢?Ò‚@y ‚C p†ç3Ѓ‰I!EÈ P8PŠOäË<ᯩ<$*P Ím¢D6ú¤Ì§Â•±%" ÷Hðì±(+! yñC†Ä‡‡…ÓŸ·Ì†DŒ`Œ,4£9‘ñ1L ˆô# IG¦C1 ”‡°\ÌÍÚâÖþ¤ò')¦2‘-\d#¢ep m+Ñ-T^Åsâ«à#jàâ(sÏÁ©Ø˜Bù‚F;q ygƒ”ªy lB’ 爂 «Rù(™4ÜÁÍŒõR8ÛþSEPÎF+fR ÿ: F‘-J€©R3Œàtˆ?ÍМ¼ ÷p‘©ZP€!u™¤Cì·ÊA¢IÿÕýçÐSéñ¤ÔÔ(6åIÏP5òܦM¥ ÓŒÊÆ8ÙÔéM#ÚxæO–"ø%%†|È4›5åàøÔ5˜ÊÆTA­§Oj¯Ø¬ƒV±l#Àîãq>•¦Zíá2pÂÐו 69E4Î1(ñ]ˆÈ£Cðø•™ò™&ü@² 'À‚0îñ‰S]~…m€vÃ…À³­íÔ'ekH(–±.]Km¡Ù#JÆC«Àè!P)T¡^sþmA€Ù–Bô¤±E…jYkQ¢.˜‚#€بÙbV¤ !(Ò…SN©MÁj[ˇǔ0…lak¶Ï6ö›¸…•wCëÈi2N¸'Iìb?–GV– p†pDÊO7²ÜïzÉ»Oš¯¬Ú.b«OpÒƒj@.Pa¾ü ‰¾Ì ë}·û§ñâÉ>H ¡´èÀ— ì(ÛCF@¼Â¤ ïM%!HÄy°Q (æ¬Cвb˦x ð,Ê‹PDÁScçY.CîÈ # `FÜ5pè(…rÔ C–bf«áv-ÆIh£ª[`9"4žK/ä&ËÏŠmΡï7>õ°×—}Ñ~“ó|ÌÂ(‘ îà~!€¸ N• ²§|¶X¹·z†Hʧ̇{ æ€ï§12S|³W{ø€$(v—'ÚÇZA}ûà­×.G‚¸Í÷: (‚wG>ÿ9X*؃øú€~d)Ê 5(æ0á¿ø¸(ø£A5Q6åC X‰¥–u<ú¬Õ›?nÂÃO\$”ÀWi’oÞ™Ožþpéc>`:$/ž`èÏt;ü”Ï=û B„PU^Ézªag>©a›B™®iàÔ(b Õ €AþñäK©™%†h g?yn@f™g–š¥P t¹O9üPØ¿JI¥ï¬)”¡q%Êl™ûœI-¢a>T,¥åì##XT×–YÖ˜Ø6˜æ•· ´Á›qÚѶ‰:¤4ØŽ+&½rêÉg>¤ˆæüž#L<ëô¤?# ç:H`ê`î0P€ÍœÉ[GfNÅá8Ÿ¢·˜r]ºÎúCÂg'p–.ñÚÃ0›ù,¼œÊk,·…6ës¡¿\I´C=gû³´Bj­ÑN?4¯r°A3‡$ô•Iõ¥lkÐHK¦xî4ñB0íè €fWùð@@L(×$þl 3D €xì4±Â÷°(À-ë4‘‚ Ipcø ¸àŠÒRøá‰wDÅ<Ä Â ôyè+¸P:ÚN¼LÛžƒ.:ég1Ä Ï9º´„b@0…:4ütŠ/³Ž[¼oŽø+s Z(ߤ¢¶8ß¹Xøü±/xû£€2¡° 0î”Ô¼áÏ€ú|’D~2 N¹å®y*â¡ç7—ñƒmγáX•£Åo[ní"ì ÷šwœ­m~“z_¹Ç˜En”Nqü*X .jp¡Lü¨Ú`÷¿–Ur;ûÙ!§wq.3^4Îa0Š”©ÀþTÀfD"™ K×€àe ¸Ç'ˆIZw$pHˆ”g g~¨fq<tkð¬g˜.D ê˜2ЉZl·>¨FYâLçÓέeœç 8;w–ù…ËD{×ÏÞ4í bó¼×Ϥ®³q¬Þi,¬®…5}gaÄO1]Àêîý‰¼ –×_Ãî2¦ e™êòÖÖ5¡IÅlG3„Á­uð¡+a ¯ãÂvÈøV„`ž‡Œ¿½Qq1OãCE»ß]–+Ÿ‘!ÍD­«Ðì²ÛÝðæª2¶»coØÁÙv€I:“ç=™®oô‚±k‹ûrr¸þŧIõFT¯à5MñO$ç” Ñâ*™±ˆ1Ó™`¢¡–šR„H¦ ¨V>§'ÐG3h)ü55¢€ÏE•!û$P ht Ô¨ôKÐö$‰}4È©¤æ®*£Qå;AŒúU‚ô<¬K§©v„G\Å ut>Ld?‚Ýǧ°•ï§d€~·¬›ØKÝÇó±/ -pÖâ#=Ôc=Qb(º7}ƒE­F¿ð`@Øwd¼ù×­ÔGkÕƒH#I¶½ÛÃÕ‚ìÕX´>+$JdH]mÕâ(;+@;$ÛE(þ÷Iڃܼ]JA­¶@ÏóGB$ÜÄÝFª›åÂ"»I‹å´PA¸}Ö½Õ2Ô³Óð[S¡·J@ÁõоÐW¥“EÕA·4¹´Kû:VeµVÿúÈd§cëdÃ5.õ¬ÕG(@sBRà!VÊä¯aVWë}C61mPÑUQW2‡!ÔìOV O z}—$8%vPþÄ[O‡H‹à:cU9&[{T¬°ÉX Î^à‡Ü4¤ÒÉ õWL.©Å¤l!eÑ=×GëñzOÂÄ—ßäÒúæpõRôFQò c0°MRb\äåS¥ä .L=™”þûeÝõxòJÃ5„“EÇœAYû>†z²[ædöɃ-­—)+ýÀ¨vï£è&UÔ<`çeDŽB¦`€¾Ï[FA]ˆ&¬Þ]«ç¤ü’›mҽÂájÒ6•Íû`S&;écBžUÍ,–é:&âôfŒN`¢Î±£¥å1êŒ g dÑn[>R&ç'ÆáPñ].íß$‹5›YædæÅò€!²¡g°Ó\RŽê@&_í¾FÓÖçâjb¶:ð0à„-ùpÔÝ·fm¶è×iWl;Ë–©Ê\ÎÅUË o¬ÈÔ’^ñé…¶Ä:î7«m‹öqü»þl&Ÿµ¦GûêæOº.’ ¶~Ù a1ÏòS•oûF„@:&ƒ‘i×–8rpX@MBŸë°^—Áq9ûqÈfmñPßð+×IÝæîâ8r™toz5Uœ¶rí•:kqÙî®Õçqñr,¤´“¨EgâÓé­ÇZ³ÅÆ´‘#KtÕäU\ºy_˜©€Q^Ê ¶lKxÓôµ#‡F:9x{‘{ ·ñe!váx»§~@/tnûMnëË_jë̳ž]øm|8uP¥ï(Na 2ø9„b®¸žJ9…+éç(ªÇwje‡&˰™vþn·— Ê.·!U{µ’¯¤^ ~—OuíGuʧ¦Û/t•ïýÙR–­k ’¶ž°“ßµ©µØ§Ú½¶ÜOtFç!ñ±ñgŠ øÌÓÁÓð‡Pa€ˆþ¤È#D±âF¦Ì¤1"‡gƒ8žD™RåJ–-]¾„3¢€[îœhæN€Ê0X‹]ˆ-à e@¦ËÒ>Ñr#€ZùÅxᦂ?h”¨X‘æžÀˆ!¨=*òdÏK;H$ '*!)°Ð€­Ûjƒ°Õ®‹- ‹Òd$-`«@²nàuEرEQFV‚ò½ÊÚ&þJã0-wLÒö\§äE°v=(P–ë£Ý59í™o^¼Ø5ÉGßC•¨•ÌÎ÷C#bÅRaÓµ‹·s[oý® ‹“fŸfqÎŒTÝ»~¯N^Tó}‡Ô6E+•ªU¬Z¹z+–¬?øPå{e!ýÔ«&÷6êo1[èËê„ܬ¨¥Z‰Šzvà(»|œˆ¡°@»ÅÜn€±F€pÁ Ì,ˆ6xÆ@µx…gªêªYÔl{|(Š„³ü à"¢%8›‡8„BTb±³3ÍÜÈ %—3L1Ç$s­5àÙ‡(J€™E(Ò€U"pþ騤Êÿ tÐBBÔVF+r˜Ý©*Vôâ™fãG!VRfÝÕc„öì³]•,ÄpR5ìõœ, a]’ùŒåMyþýxaË:ÒîÆ™JÎçdŠÑäÔ ¥úÕXy8ëuO”J`f}ò)õTÛØdŸ;æ7e@yOµ×f»m·ß†;n¹ç®ˆRr±]û# ‹Ã›î Ÿ‡´ÛoÃG\J³P1–1L¾ƒ‰šA=; Q&]ø™à˜ƒ· (hÆ›hG<걎¶X+2ǸRŽq“!™:ºÍ#žÄÀ>Ø…I‘üyÕ>ú! Aæª}tãJHS\ÑŒ§dÉ÷V²žB\®i[Û%-‰H·õD| €OB¹”*ÔãC‘%Û‚þ¹”a¾‘–³ldá‡'4 µ`Ç à€]“–#G+³ÀŒu1•ɸ%™¤@ ý¡• XÍR±¦^éFW˜G!Š×jØn¨†öÑ*4™€/úÍÎG' ä“zû\ :Ѥ&ˆdí\irMÝ’Q¿‹d!¼0…ªµQà@‹¼RA„Ø¡Ÿÿôœ.jµŽZ&àä×Ö­t*¤ibçJhbŠ+rÀ ÓC(ªlUSZÁª£Å+¨>ù©(&4j…•(úSB!ô©•êJ|ªƒ.ÕŸ¹jTéD¥j5|‚•¸Ô˜Š4DJ/õa N V¨Ô(ÚñfD`þR¦:µ®YŧR±I–d­m†¸ô:QµÈ•©ƒóGXíZ‘ì°#¯æ4cÂÚфմƒ({§ôÀ/â‘Z™§^ÒÉJ¤fîq³:ßGϧ(Ë\% øô">ñŠ_<"²œ¸;,®gZ:\{§Ð(Õ·þëe³8f‘.BÝÚèú9<êû8²>ö–T6¯n¹Ä)N÷«ÓRA$Ô:Ø™vv³]$^Ï»Ø­Ž±e:ßÜ·úíñSDÃ8@-J`„PÏ}@Ÿ7eJîß:þ“š3ÁB¦>as“#)ï·)"Ðòë´Ó3á´x±/”DZæ´À¾Kߌ—Ô©¹ÈÓ‹Š D'/¶t‹ËWò–S}Ùbl~yPÍô9±dôÃ/>êUÏú`ªNúX=±»Nzgš~9©çGúû^ö—]ˆ#3Ö£[Ýíh¿ýÄ¿ó“?쨣ñ¿ö ?„€¿$·VÊT¸5R»ÄÚ¿Û;@gJ@óC?ÖÓ«e¸½ÇS¡3Û¯D³¥v88— |[›^:;‹Àˆß* pA—˜y@3 ÁŸC3‰°µŠð’9 †½Q ÚAKS4€Š %4j[{.O ³@þ3°ؘ¡+Žeà=ޏÁŠ §ëÀÂçÖ(l¸‚7¶0Œ'T‰¬Áˆpà ùˆêëˆ5lCû«8L 9¬½:4;Ž˜B†ˆ6„°\ñÃ!»ÃÊC6ÌW8¹7ŒÁ8¤A<¬C©X7µµëÀ \Ä<¹Ã¡!‰A¢ ëÒ ¹p§Ðø8ƒ¯À‰v¸™¶ñ"1‘ú€ áþðˆÅÀpâr4âBæH Óa†~¨$®b40@F—›I‘zû4v8Ž`È;CŒy f€, 3¤.üCHë§ ’ç ¼pÅuÀ oTŽˆX}(§ ™Š¹ŠlþäÊ0Â\\ _¬Œa} FùðGqÑÆïX¼“h¶ÈŽ}x’Â’pTÆØ‘ˆüE²*§m3Á’^l±È!I # $ISÑ8PŽ‘߀†¤€ɃÂŒKÅÊšQ6Á–_é¾\á˜uª¦—Șɇ‰Ñ‹Á‡Éw!”(Ê}pJKGé+‹u±1ÐÎK¨tÂ)¤ K4 ”LR¾149˜ ˜›ÚË<þЗñ‰ÊvÁóé”ΨKšBšÁl•AIšYY) Yù“ª$Œăy­|LB!2ÂLÊtÌA”šþS©ˆ¼Ü±YšŸQ™„Œ,š†)ªä˜EIǬ†JÄ^aK®¹Åˆ k`—ݺM¥M•AD´Êç„Η u ?ÅQ%¿9Ë{`úyÊèôΖ Y@¯ï$OÚ.f²‹gà<;žô$÷DÏëÄNY¸<” 4*³óø,Ï¥`’|ø¥ÿ$ÐÂí±(<ñ|h‡GH›õÑžr“¥÷Y¦Ãi!ml ú„!  -Ð:ù…'èÎ5ÑEÑUÑeÑuхѕѥѵQ ]ØÊÝ J!Qp! } è£!‚"$íð‚z­Éñ"ÇÃþ 0¢N¢"¸¹Ò1q"RR1á"·á¡}˜¡ýùÒ0 S)u‰fY…Ë"RâP”ˆx`®¶q=˜¸SÄÉȤœ;‚¤­ ë¤ÖJµÉS†+Ô˜8Tõ«Î2€OúS·™$íaTTZ L:ŸAÍHES— f¶6e ý¬“2H‡IO;¤F½TWU;3ÉyÓ1±ª!»BJT<ÝÕ—XTïëU-m<@]‰fz¦hJÓ“KÂ&mâ&6åÊ • 9;G$ÕýéÊ£ê•㛊ВÁ.P‡ôy|z;„r,ò+¬+ö誕؀9…õ*”J€4€L†z6ý»ˆ©þüÀ8+xiÅ:W”zªxͪ”ª•|ÐVå‹™¢3$ø(]á*TÈ)ϹžÚÕ#¡RL„P)£ªXÍ£ª}0×’ù*€RWHªv}ª„…dC {Ù šWºJY²jW•x×wXµj† l7†E6”“¬¿²Ô!“D£A¾2ZÉÊ*ݧ¿#¬y0¬NŪñ¡½*Ú¦BY±jY¨Ók½À¸ cÔWl oÕ)^@®!Q¶Á/ û‹#ë1Ú21Sй]±—™‡G€ƒÈ1Ú 8èÃÀ“PÚt1ÁEÂM2c2¾)¯ó:¸w#öjÖ #킊ùâŽþUj†ûb6P0àZ/Ü0øR†ò*°ñ¼ [°+\/² ãÛÛ•°&3V Û}-ÊåÎø±ÐÛ&[ÝÌ£{ˆÜS7 5¼Š €#“­ã° »\ô.Ñ2; îå]âmB«… ‡‡Ø€ŸE¬Ã2»­ÜÄ8±«’3B²}$D´5sÕõZ­¢HÐ ]°Å¿<®[6oû³kã…Qµ>éäÃ>”c+8$­v£²÷Ý?²à>#‹6˜M(Pk[ã\”0‹S3RUS‰˜Îq=Y댒.*Öî3@ápKZB]¶@â§£%Mb!F‰_½þH´£1fXbÀ¶˜ÈŽu˜€>üáoƒ` ¶&É«M ¡ #Zwû8xë2/¾â™‹bý…Ø›’9€ûË1[ßýâVJ8HãÞÄ“º°Û»Åsâ Ž0€—˜£cY£F¬=D¶à—'^ä·ÛÙÓ»ª Jé¸'jáˆÈ9𗼡èCE”À&¢S6Ž8ºZQ:#M¾[ë:˜+äMæ;"æÅcàA檨›ºCV‹WƒÕÇUdu«²·êB&”`ÖºŠüCI¼;LfNÎU žÁc÷…¶·ÖŠ`f¸3dk&D$V9Ž:þ½‹†uPÍ•À¦ëØ5áíÏû¼YþmbX[döC@wK=ô“¢D~Áþãg >~ÆàA£oÓˆ°d‚h?e5¿{øžlŒ¥4RÓQT^íóËU:Öçvb>ç[º:>âAJé @½J=Œ&¿ŒK€þ„Л`p.è™V@¼éb®ˆˆÞgÈ?‡øàÃûé Ôé—¦¬¹;w@‚ÿsêñƒéekÖÈU¸‘C¨˜<\€dxè$¿D¿a{)>EçÎÉÁë8êK»„ `Ì+Ö>ˆõPKµ©ÃQÌK´Ác®¿ì :H.Än^Žß,êclŒl¾¡Žf‘BÇÆë°tÜây®/܈þ:´ÂÎf3fQˆPCH mEŽlÊšlDמÃGÜÃlmÔþˆ@$씈è—úÄC\lJLÐ!{íÔ6XZ nR4 :dín_ê2›ßîH³VÄJüC*üÌ–T~ëÎYE'€â:.]í@­-„Єf‚”©ˆ” ‰„ïâ‰h”|Œ‹º ø…áø YÐH(¹±U4 ÉoäÅ€üÅhåÆÎúGTÆð(Çsliu¼ ¼ê—tŽ‚ ieàFÿN…Œ©ï4ÈýàÇŸÜwüHl̰RKqû i’Œ’œG”Lú^I „}Tþ‹Hî~cÿ û^È cð’%p Ú)ù +yr#ß|h5]þîæQ¨¶´½m–BT ‚sΖiMÜ„‡©\•€‘ÙQŽˆè¦}•ž1€êp Zt¢~¨3Ëð`hŠèó°¡s«LaƒI—ŠÈ™FßKMêµm+±œáÀ$D9'ÌL±OQ†Ä¼Ë ùQ‡ÁtHWÎÜŒM=ߘ”¹Ji,Y÷ó©|õ‡¢Ì<·Lü&l˜™šI[K'–9ßuØÄИͨ.2ŒA¥ÉšÉŒõ_"²è Q ¡COtc×KH—•×lš²®27w¸ÉÚæ1U=õíÔZ=wjœþŒw²•ÏþÌãU£À>m€ù”VºÉ~P™€÷âÏÌMž{¸z'[5bŸö8熺ÅPê9„SŽœ %Ð6'%”å–Qÿ‰[‡7ù“Gù”Wù•gù–wù—‡ù˜—yÍÑ=gÑš‡ózªysj!îù„r“œg›2õÒ.M%¤W›SÀûT0‰ÒiÒÚ××é$£o¬“,®­Ç“3%z¹{˜ˆú) ih ú”(ølù:§3Ô`eT^%ê­ÿ÷øôÓ§POwkµŒ@š_]V¹äV-‰GÅ#½—‰I¥žg$&LͤŠãTBä{2É—¸[ûþÞ¹û†ÉX- ‡¿O$ç废¿·i{Âá¿¶10’Õ¸¡Á_}¸Q|ÑC}$VýÔ›Ö‡‰€&!˜&@mÀÈ‚vVûvü•'\ûiOvÚ 5Œ'7J°crm×µY°ÅYšU Ÿ*+ø×­Q,™âªT§TÙv ëg[¨…ê]©…½9µ`E!ª•ˆT8ø+hð`ÁÊ.,8BÞ¾}‹ú +ž¾}¤ˆ((ÀÌ»}æ˜PDhPÀ-Sn¨ñ¥"‚#E4‡„ ÅxGáèhÒ`Ë—cV)’dÁ ¿ìíË'*‚?.…UÊÔ)ÔŸ9<þºèÇ#Zmú4êT˜%Mjˆó.c¾@4KD0Ä4@>$›xð#¼£daÜwV«A¯ØYøk&ðؤKÍBívh€óþð7eóܺwóî]æ÷äã£$_=ìQֺƈgÓ®mû6îܺwóÞ-À–;'/€µÓ1WÙÄ/ó ­Õ À˽{ÿÀ` º!  €»&+ÖèûãýÖº +Z$1PñzöíÝ[Ç®{G*õÅPñâBB0¬L;9T$†>Ÿ$ƒ3 PrËᄞz칇˜B¡°0î—P2 ”€€-ü⎠„̰CCm ,#¢G  Ð þþ$ÀL(,ºãoá©°Æ=>4w]‡”T_|øù£À ë˜"1hÑá‡Z1y_wAŠG¤yà’^ .$±Ú–òÇŽzf®F|\z^*¤qÏ D&{hÆ©ælúñç€8c—?xÒB9z9žç­ÓD™gÖ€,ŒdKx扛ëYš&~"Ècž?é¥h‰æÅ¨2˜PôDú i›–$Å©>Q÷+°Á ;,±ñ:A 8ãI²ÁoƸ ³¶0¶GšÔ:$=·VÄ ³ðbJ?që­m]¤ÃnAúí0[ÔWÕŽ¨Œ'c¸óDCæ¢{ÛþŽ~ªo£Úv ͽ {m|åbŽf¸âš Š´?á…*Bî‹P ?ˆA¬bÒ±ºÝînGæzb€ÀénëòI¼œbóm뾬1¹4ŸËóA>Û&/B¬"Š Œ†HQÆ1ó±ÀY݆©¦ÆÂ¬#¹Ԭöº@./>Ù*ÓNçÈ+ÐTëL4B (£d±uÛ}7Þ¿‚¢G·¨"B2mUÔ#.mbFT\»aFFB©ä4í8rCIF·‹³AH#äÁ/¹£²¯¥P?û,zЕ_®­ArËZ-ªd%ãÄ ë”:²/,쯥ÀÓùÎp`Üã•)Vÿdþª¾¤yA¶O?¤ Ðì(©mÔCyDÍSn9æEs^Pëî¼¾9¼3¿Rëèg¾~ õÌ‹$Ú¤ ÿ8ùÊG?×¥ïR‹‘?ä'¹º¯€¡ž©^²ŽžøCiÿtT¼‚€BÉÇäê÷>„`³Ë S¨Â¼íÍ[Þ‹€Š¢±ï$~sbðððBá[ßÞî³ ¸Ç'¬F½ŽŒ óØ_씊 ˜ ô@–£‡NpIFD"·« Ù.! ¡ˆÜV„XqÇѱ¾¬EÔÑýÇä¹Cˆ @@ð~G¸é­O!jdãO&¢†þÏqìãv¶H1AJÄ_E¾˜ÄÚˆ’‘œd&ø ž-1ž‹]3ü´½Ü1ˆ‘üc…aÉK‰3xœ'%ɨJnòë“Âr@ TÈFYª|ÕÿéGP @“pãËiV(ÍiR37S¸bg°¬f-„lìHmXE”»Šþà!`'Þ« åÄÍ;rÊ‚ÔKŽ–qc¾X!²yHzþÀgÚ©‡)D_%ÔW¢¨aÏgô.qô§IÊ2‰Ï¡Òp#:k¸¯E°Ìñ¤§B rÏÈMD `†¾p…7Àí£ç”G:s8yÔ¥™)ð ¢Ò7P!.WLÃNŸØÔþ”úÛb2f檦y„ZÒB'DІìnXC`Fs8TwÕ # Gž8P49u©>9(Ô`ªÎ­D*r¬&\ã*׊Ü"8ÃñÐöΘ€²Ñ ª»‚ØÝêõ aî7ùh\à†t J`Á Ò€ –°5,Ù`µ¾y>‰¨å,”Ÿ 4«Èxs }ld#…!eLêúÁYo`NnŸecH÷Vúà‚Mš -ÚÑ„ã–T¤ÕÓ-Åh6X»%ɨ‘ÙÞz´mÌ뤮”Ünåú ›AD íêÉÒ}ôZÈÞ)Oô\¯eçÙÉúdþæEï}›…º±ðe¬cé+Û„²7æü§,òlarñ§–[XZ6±ñ­ˆcW[Äù÷¼%QLÖ*R`Ë7Á±¥¬v­„9^Eï9(¦$Láyä´"eÓp{;¬Xö¾Ò+å\“¬d»%@ LA¦f¨n&Cò&€/„¤†À‰Q4$5F$NÖ–»üåÄqy^&gýA‚#6Å=ˆÙ‘‘~‚›&¥r@æšœy6r3SÎ1„’Àn^óœ=˜; ÀwÕpcmB€ ~ä0H€ñh’:ôÒ1 3)`D›Þ,g:‹äÎ þ1G`Îq““”íÎi³¬Ã‘TŒ¹Ìæ(´G€ýeAëÄÌhÎ…;|}eƒ9$ùHµŽÈœgO[ÍÁ‹s“™²sô€"~4šÂLìŠ ÚÌ>Ü«;È›˜:&$bf7<2rl4ÃúÍšA•ܪã £ƒêbïCû>È– .¹â¿8Æ3.M‡iò“¯üå·üøÌ>ô£/}:úÖ¿>ö³¯•êk¿ûÞÿ>ò¹þñ“¿ü¯¿ùÓ¯þõkýì?üãOx÷Ë¿þö¿óÿýó¿ÿ&§¿ÿ  ±!ùþ!þmatt@eden: ~/tappy,3$$þ8ПÁƒ*TH°á‡6$±"É-j4Ø0@ÁJyvÈ#;<ÛÇ’¥) 6è²ç¯dF”þ€XÁs ½Di BÒ$΄xŠào †f‡>mêÝMj(zsje  pÔÊõäTUêù0ëªÑ©n©b:6ÀS 7\yS!!€½}>U„•c(ª-JqÀì†gùJ"äYòÁ)óþ˜mÚåB är×csÎÑ¥‚q—!AÏ^Ä({öÄÚ [êf‰ánݽþnÜàpÞÅï+îO9sçÉ?—}øtëÕ_מ}÷vï݇'>yð€!ùþ!þmatt@eden: ~/tappy,E þ8ПÁƒ*\Ȱ¡Ã‡#JœH±¢Å‹3j¬¸¯£GCnI²¤É“(Sª4ù±eH‚+cÊœI³¦Í‡2P0(J¦ [~$¸áW7 @±3©Ó§<Å,5â5TDÁJS–ºJ*´ãÀ ÌPÑ@êo N¤ºRÀ‡ ªJ0kÛ› l@@Uâ†^ÜÁE`@­ÀÚ&íd‰Z±š!‰U' Ëš ໵l» Ëñ´C3U¥ðDÆÆIÊ«¢cÜ*@(`7(—&@,gñI&øäIn1@mÝn؆H l'ϬCþ‹H¥ÞŸý©ït µC¼f˜´)Jüˆ¸ußïÍ0À”52Ü'€/Ç Q“)¦tÒQgal£ DÁÊ…v(^Y¤ÝbŠuPMKŠh%4ûèsZ  K<ûä3 T¹ã>3R5Â4„´ÈK(cñ$Ë:t-€$›Ô·Cì2ΓâŒ#6„Íâ8â\s„–Z¨Á1Wf¹¥?¯ŒƒÍþx¡æm@‹6ÜtƒŒÄÂÍžÒ€ž{ZÍbôÒÍ—y`L74wôÁÇ{ðQ`%TÊGtæEhê‡ þ”ÀÇ©¨öQƒ?±Gþ}Ô1\[thʇ±”pHl÷Pfb1$Ú>hÐÌ!è $c 3D @•0¬ÐÂ/í°–3¡Ä 0îè@ÊœDàŠ›À2  ÇÁ3‰$7‚<ôÀd€¯¾ôÕç BˆqÌ“ßs %yÁr ™XáOàdé”GÐ8ã\SfÅ"€óMŠC  ÊM5ÀЉÁsr°L'4ÀǬ&6«?\Hèƒ}TºÇz¬º¥wð¡Gè+¥zúI[zG´ºéзÀ ªÒyjP„•,Éa'ìx!Ð+’z«5Ô5@î"ZmÐŒþ'Ü­•±| Ëiþ”çÃvr%ÂS¿I6î8 {å&‰¯,ð€1âLñÇNIÀæ ¨& d“¹Ù|s„ÙcoSÍ ˆœA=W$OÆÍކözÐdPÄÇ 'èA‡Q@'4ê­TƒÓ´Ñ5H€Ñ0¨ÇXœ>ã×Y'ši"6Bà ‚U‚ZŠÑ‹Î¢Õàš5ðš4Åx)”1— y€+º">J°‚bßxŒEx-=åqãAB@>ú£Ïþèc!µ²-Oàn€é§§¼P9P#~ Ù@>òJZò’ã@X[ð ÏÀ™ûœ ›¤¦€.NÂF‹|ÁÉÌù*!aà†4N ÄèŽ Ú h¤DnÈM ( ï¢1•ƒpAÈt8å(&Úàzz` ©òàµh%N«ž÷Æ"½<³F€ #Cz&œ‡Àœ€œ?…‡’…CdÇ `ò0j|Z° ÿP!=òÑnÌX‡`Œ|ü`\Ê`‡^Ð×@F.ýX Bºö(ä4ëDf!%o„cMRðØÂ®D tLß(“æ"ZÐf’…ß(¡Cv¨=AA§ÙèþF7¢ŒYù²РíbrÖUƒt;8PžéÀ“b i±Âƒóð4JõÁY€¤øðªÀS|tµªâpƒ ¼4ÈÆÉˆ“nH(‰càØ"€/¼cí@„VH {ÄÈGáŸ9à±ñS怑"1Ü£D Q»v0¼\dWß #r`)c«ûì5@K ­@L¦ê"^ƒ(€ ¿ˆƒ6¶¡;À´NrðTh±: £)@ŒD¬§JxŠš| .«Œ €(÷TKûŠ«¦X‡Û]WT«@ ªÆ)ôls!µ“,C` Mü°!í#îQˆdYD€ÔA3²C¨ Çúƒ²—y“‚QÖpžv^HÈ1‡ádTQ7|w’0‡›ü!L5猄™4l¸G Ðè†ìó‘ý£Îôx'ÒsL4è2j`îâ_’èCÈ bÐ PI˜£ÁD+¤g‡*ñEM¦°…" Èt‚…1·ã öGtñ]פÔóT!B€Q‘‘ "RàE»!Œ¬¬D²XB:m«È`Ü,M@!ùN!þmatt@eden: ~/tappy,E H° Áƒ*\Ȱ¡Ã‡#& !ùNþ!þmatt@eden: ~/tappy,ý;././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1459340366.0 tap.py-3.1/docs/images/tap.png0000644000076500000240000006346200000000000015123 0ustar00mattstaff‰PNG  IHDR¾¾‹tµbKGDÿÿÿ ½§“ pHYs  šœtIMEß £j)ï IDATxÚì½w˜Õ•÷ÿ©ª®Î=Ý““ÂŒf4Ê("ƒŒ²@B,Œ0¶„ýb/^¿Æïúݵ÷çŸÁaí5°Y–`P0A "(§‘f¤49õLçT]áýCž±Fš(‚µÐßç™GzºªoWû­s¿çÜsoA )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤B )¤ð?§ùc†aô{<øÂ Ô._Žø÷¾<~?wðõÆÄöî¥éþ~Î:`Ͷ9ðá+ ”>MËý÷ãß°áìëß \I^Þ^–-»–iÓ‚> #¯»ŽÚ3ÎmógÀw?0ð€yÄJ¦fæLâ'NtuZpȽ€kßLï²MÚ¢Eþñœ(*ÂP”®s&/Ð6ÿÜ  9ßúy¿úUÿD>U*žÝG)|bhi™Á¶mOáóÙ»>ªý§:÷¬þ)e¬Ï!ñdío^BûÚ;»-CU1‰O¦}Ã]GFûnÿøñ¥üö·/ Ëv´g|ø9`ê­}=Ã@…>Ûè‘È™ö哱},vÑßt1]ŒeìXrî½·[u>úèGj/ûÞ{ϸøE/_õ£'†·Ÿ¶?’ÆŸ`Ó¸ö¤ñ'ÆC"⧬ñM¤ð飼|1Vën ,eŒ”ÔéM'F?Â×£ƒh?ñÚ ¢}µÏƒñø˜þG 0@ÛÁ¿w¡| BÁ6JŠøƒÇlàš³?óóŸ_‰DPU“É„ÃéD¢±J",ËH’D"q†¿‹åì¯/Fõ÷ƒcñ‹+"‘ª¦a1›‡z½÷õýPôã_ -lÀ¿‘~ڶΡ4šPL’„Ãá(~0Àµçõøî96V5 5™ÄjµbµZ1 ƒp$‚¦ª˜dyF/ퟞMiüóñcà_žþyvìØAÁر|÷[ß ì=~œ??ù$6›µk×2*+‹½ÇÓéóqí¬Y¼»oš®wß”æ÷÷žÊE¢Ñ(oíßÏêÕ«5|8;?üI’†æ6ûh¿ ’ÇóÑF¼~ÚjÛš¦1{Ö,jêëyâ‰'¸fÚ4ìv;º®÷Û¾H¢ÈUÓÏ„ïîßO4¥°°ÉÅÅkhà‰'ž ‘Hðõ¯‰#F4 ~úƒÐÑÑÁ’%K¸þúëÞ¾ð¹ÔøÑ¨Þç1»]Œ:tˆI“&q饗òãÿ˜#=Æ®mÛøË_þÂøáÃé<}šgŸ}–ŸzŠ-[¶P”™É»[¶0sìXdYæÍ›Qe@#šL&:;;ùÍo~ÃeãÆ‘m±ðæ_ÿŠÉd"™LÍs²,xŽ®ë}¶m6›aÀà_TUEÓN8J’„$IÝí&“I&ÅÑ>à—?ü!ê}÷‘‘‘ªª:+³ÙÌŒ1cm6••ÅU“'søða-ZDSSkÖ¬aL~>>Ÿn¸wß}—+¯¼’ýèG]M…£zôbqøŸ.ñËʪûT” ãxä‘G$‰uëÖñÎ;ïpýõ׳yóf–/_Îúõëyæ™gp»Ý<öØc,^¼˜×_¯ýëH’„¢(X,DQø’$aµZeA0›Ídff2jÔ¨!>ÌQª««$ÅbaÌßÈs6t]§²²’d29àuëºNqq1ÙÙÙý>$‰D‚öövÚÚÚ¡ÇCÐõZ­V, &“iÀkﲑªªóå/™ªª*/^LSS<ð>ø ~¿¿›ô‹/fýúõØl6þô§?±jÕ*ʪÊ.š™£O•øJ¿±–Ùlæ‘GÁívóÐCñòË/³téR¶lÙ²eËX¿~=ëÖ­CÖ­[Ǽyóxýõ×)..&‹ ÊöUUÉÍÍí–¶¶6ªªªú®“É$£GféÒ¥½ÔÔÔ 8r$“I¦L™ÂÔ©S%mª««yõÕWñ É]×±Ùl|å+_¡²²’… ÒØØØMú³=ý¢E‹ºI¿jÕ*víÚÅ]wÝE£ÒxÑÿS¾ ¡Ÿ?´{‚‚~úÓŸòÀàñxx饗˜={v7ùc±>ú(k×®¥ººšùóçSUU…ÇãÁãñ YªœÛ¹CÅ`~Ï0 FŽÙçñáÇú·’&gjcÆŒá¶ÛnÃf³]нýiš†Ùl¦²²’ ô ý¹ž~ãÆÝ±ØSO=ÅðáÃÏ„ ývÿgšød÷õ‰èi“'OfóæÍäççŸGþ«®ºªOò/^¼˜“'O²jÕ*233Q” ˦]H€5˜ïȲ̈#ú%¾(~2]‘““ÃŒ3.Ø!(ŠBvv6‡Ã‡³`Á‚óäÍÒ¥K{È«ÕÊÚµkyì±Ç(--å‘GA1KýÿY'þ‰¾þ~óß·/S¦LaëÖ­=<zzzÏË-·Ç{Ñ¢Ex½^î¾ûn²²².ˆüº®£iªªöDvÓõï@ÞÒív“Ýwßàr¹.Ø+ûý~vìØÁ¡C‡zÕþcÿüuæ\Q ¸óÎ;{²}‘þ…^èöô]¤ßºu+cÆŒáè;W÷×ÿŸõ<¾§oÏiç·¿ý722ŠY»v-[¶laÉ’%üô§?èÖü]ïÍ7ß̆ xôo5%]šÛ¶mÜu×]üéOÂëõbd~^–exüñÇ»³ %%%çi~UUyúé§ ƒH’„ªªýz}MÓ(((è¡ß»Ù®ÀÒn·“““ÃÉ“'/Èówttðúë¯#Š¢p饗ö8ž‘‘1ä+‘HPPPÀêÕ«9qâÄ€¤ïÒô÷Þ{oÒ—––²ÿƒýDÇEMx¸HpÑTgšÍ&üþ÷Þ{/Ï=÷“'Oæ•W^éöüßÿþ÷Ï“=7ß|s¯šßëõr×]w Éó ‚@2™ÄëõÒÑÑA{{;Á`°W½ÞÙÙ‰×ëÅëõ8ö¦ï›ššðz{–áŒ1₃sQ±Z­˜L&N:uÞq‹ÅÒ=É4TÒWUU'o|>_7é-ZĆ º=ýºuëzþá‡æ;÷}«ÅÊÅ„‹†øŠ¢pÝuK‘$‰Ûo¿gŸ}–)S¦°yóf x衇ºeÏ‹/¾Ø§æ¯ªªbÞ¼yÝäŠæ?;õg2™úô¾]çtý ”6lØ9Ù­FZ[[ÏÓù¥zÀTUíNÓž{_ƒ_ºäÍÝwßMUUóçÏï7e¹aìV+kÖ¬9ÏÓ?öØcÜÿýhº†I2¥ˆß"‘W_½„§Ÿ~€;›ü[¶l9OóŸMþ[n¹¥›ükÖ¬¡ººšyóæÑÞÞÎÝwßý‘Þšt»Ýdeeõø¼µµ•ŽŽŽŸåçç$oÙÙÙÌ8UŠ¢Ç$"‘ ??Ÿ»ï¾›ãÇ÷Kú%K–tË›µk×òøãSZZÊ«¯¾ÚMúµk×b·ÛùÑ„ ^YôY&¾$I´µæÖ[oå™gžéAþ®lO_ïæÍ›Y¶lñxœuëÖõ=]ä¿Ð€÷£@Ó4 ÏËÏwI¥³a³ÙÈÍÍtºòÜàøk_ûk×®¥  à¼ã­­­~G§.O¿zõjŽ?ÞgöæwÞaÑ¢EÝ칚¾¤¤„Çœµk×b³ÙذasÍ!v‘-F¹hˆ/Ë2UU<ýôÓÜzë­<ûì³zþÞ4ÿ¹Ùž³5ÿ§íù Ã8/™L& ƒ½ÆCÉ矫á {•9ï½÷^¿ñƒ¢(äçç,šþñÇgÍš5X­V6mÚÄÂ… Ù÷i®ñ{SØH’‰Ý»wóì³Ï²|ùònÏÿå/¹;àíMöôð®Y³†ªª*æÎ‹×ëeõêÕdeeÇ?ÒD×`Iß›¾…BD"B¡Ðy^pĈIç÷&µ^{í5***ÎËn%“IE!‹uË›'NtË›ïÿû}júÞR–ç’~ãÆÌ;—ßýîw-?ú±Þ×ÿÄtfŸH&ÆŽÊÚµÃÑ4p8Ì­·ÞŠaÜ~ûíÜ~ûí¬X±‚Í›7w§:AàÁäÅ_ìµ¼à±Çë.o¸çž{¨­­¥­­‡~xȹí¡.33ó<}ïõz‰Åb¨ªŠßïÇf³uËËË#--p8<¤´fW`kF·íêëë9pàuuu˜Íæî½ë~oºé&rssÑ4ÒÒR***X¸p!MMM|ï{ßã'?ùIŸµ7ý‘Þf³±~ýz-ZD àŠ+®ÀfبLV¦ˆß—öxrðxzeÅŠÜ~ûí¬\¹Ã0¸í¶ÛºÉÿÐCaF<WÀ{nmÏüùóyýõ×3f n·»>n¨ªJAAÁyž®KxÎ)-¶Z­äææâ÷û=ÿÐÐÐÀ† EUUI$ÝÁì¹ítÝoii)yygJî9Òƒô=ôP<ýÙµ7yú 6°hÑ"Ün7n·›¨7ÊQíhŠø}éÔ£GwóÖ[{º;«+8\±b’$±bÅ î¸ãA`ÅŠlÙ²…Å‹Ÿ7ɵtéÒî€wýúõ=&¹æÏŸÏÆ),,¤´´ô#¶ „ÞêsœN'NgßëGFŒAeåмc2™¤³³“ÉÔºìëÁéI¶nÝʪU«(//gÉ’%ƒšœˆôýë_Y°`6l ½½½{‚¯$¯ËKŠø}åÐÃá õõõ=†ÿ††Eá–[nén¿ýönÏßù»dOù_xá}ôQ Ãà±ÇãÉ'Ÿä¿ø7Þxã'ìv•!Ÿ«ïƒ ÑùgÏ? fdMKKcÑ¢Etvv²`ÁZ[[{­²Š¼Ù´i ,àé§Ÿ¦¢¢¢;“•L&ɲeáÜ)â÷i‹"²,÷èx“ÉÄÁƒ1 ƒ[n¹Ã0X¹r倞ÿ¥—^:Ïó¯[·ŽK/½”U«VñÁððÃóÜsÏ}"²-;;›ÌÌÌ!777—´´´î’ˆOÂÆ‘H„]»vqýõ׳fÍB¡PìÍÎ;{%}II [¶lé“ôÏ<ó •••=—aŸXÞg$¸…¾æW¬V+‡B–/_ÞMþ³Þ¾dO—æ¿îºëxñÅY½z5¬_¿UU?‘Né’hç7‰ðÆotË+‹Å¼yózÈ‹ÅB^^>Ÿï!¾ hšÆöíÛq¹\üð‡?ìYo»í6vîÜÙ²<·Ê²7O¿qãÆnÒWTTô>sŒ€†ö¹&þ{}5JžL}ÖîZ­V<³ÍË­·ÞŠ ÜvÛm=dÏÙÙž³=ÿ‚ p8X,Ž9ÂK/½ô‰{¡Þô}{{;{öìAÅnO8cÆ rssÏ“;ÇŽûÄcªwß}MÓ˜7o;wîdçÎ,\¸7ö ýèÑ£ûõôO?ý4•••}Î#¨¨dŠ™>àØç•ø³û:°j•ãûÛ¶é?éïËg“ùòåÜvÛmÜqÇÝÿß²e ‹-ê&ÿƒ>È›o¾‰ÓéäèÑ£lÚ´©ß:œÞÁ³Ñµ ¯?}o³Ùz]ÂèóùE±{§EQðûýçôèÑlß¾Ã0Îû­Þôÿ@+·úòü‹…wÞy§{Ô4›ÍÌ™3çp]JêœAëç’¿+϶æßºuk·ì™6mË—/§¼¼œ7" I8|øðy2¦¿í†a`µZ©©©9ïw*++{ÈA(//ïÞºãì,Åb9ïsI’¨««;Oy½Þ ÁÎ&ÿM7ÝÀ=÷ÜÃþð‡¤ïª½9;{3§?פŸc©£ñóÁÉà<Í¿qãFž{î9®¿þzŽ9¦M›Mú.ÏÚÚÚÊúõëÏ;f6›ûl§kë’M›67G`2™zxlY–)//çСCçyã®ÎýþÁƒÙ·o_¯I Õü]²`Þ¼y0vìX^~ùånOßU{s¶¦éÿÞÍÂçøË–y>âŸM~AΛáF£¬^½šË.»Œòòò!“þlB³IÕ a6›Ñu½û¯kvµ¿"´®kìï:Ï}x>®€×l6óÎ;ï ?úѸÿþûq»Ý¬[·Ž{ï½·» aáÂ…Cõôïÿôe}[ÏúÏ.ñÿã?ò>ö­¢¢‚@ ÀŠ+E‘+V°}ûvV¯^=dyó‘dz¿-]ÔuýÌÎoG÷ìeZZZw€ÝEÜ®‡!‰Dðûý‚Á Ñh]×{l òI¢Ëó¿ýöÛ†Á¼yó8xð ÷Þ{/f³™ 6°páBž}öÙ "=ÀäýÇç“øEE}-­ÔÐõKEqÐ1]Ä‘e¹;¸|ùr† ƬY³8vì/¾øb¯ìP~c0× ª*º®ãt:)(( ¨¨ˆáÇ“››{A …B´´´P[[ËéÓ§imm%‘Ht/ùXª>l`2™xÿý÷1™LÌ™3‡Ÿÿüç”””°xñbb±uuuØíöA?ˆ]/@Ññ¢Ï§Ô¨,&â÷û-/ºˆßÐЀªªìÙ³‡U«V±gÏþò—¿twpo~nY°aCªÛé"¼ÉdbäÈ‘Lœ8‘²²²î 3÷áÀ½T;FÍÉ“Ô74Ðéó‡Q”3¬Ëå"+3ââ"ÊÊÆ0vÜXJJF3zô™?8SO_YYɱcǺWn õ8÷޺’ Ã0xñÅéììäŸÿùŸ¨¯¯'‰ µùUw*SU»—pÑJ”‹)«“|íµ×xä‘G†üÅŸüäïYПýìgTTT º®½«ã‡: ‚À¸qã¸ôÒK{äì++ÊyõÕ­lßþ.'ªëˆ$Ì8œ™XÙ8\™ ç£K6™8 Pœã >¶¼µ‡HàEL¢Jv–™)—ŒfîÜk¸öÚ¹œ=®ºê****سgMMMC’?]öèºß®ÊÕþðë_ÿš;3úúz¶oß~Á;}út¾ño\T3XŸòÀS>«/©S[;ö®H¤ækuuµ$7ºÈ˜øÛ.ʃMYNž<™¬¬,B¡Ð ¶ðëú-I’HKKÀ×ÙÁ Ï?Ëú/q¤²…ÌÜ2.™:›Ò²2ÙE—âÉ-cÊ„<&epøH%ÑP€æ†f±¥Ù…b3 ²">ÍL}Ì‚jI§ l<’$’БíYX¬nü1º†fŽÚGÕñ÷–kæž{îàλVc³Ú{\[<0¥iµZ©ªªâ¥—^ê®ßÿ$mÜeçü´|²We³äôú!¾ð¹$þÏ>–xüüð‡¿’Wôôôn/8˜ÎìªWÑu矞9sæð»ßýî<ÝÜuž¦iL:•9sæ`³ÙˆF"<ü«ÿàw|Ù9މ—-ÁéÌB$ÂÌR—ÚNG](QÈ8=nbÑ0§kÛpéFzLX“d !ÝL›¹€`'æ÷’å±"¹rHËÊÁlKCÁD0¨³çÃ|¸û5òsàÿïÿÅÊ•«€3ûê¼úê«ÔÔÔ`±XzÝyYUU¾öµ¯±cÇ–/_ÞH¬½†jã³Gš³pÿk÷3·jîECü‹Fã'qæÏ_@?ÿùÏ1™L†qžÇìÚgG–e,XнIÓ«[_á»ü_:£nf]ùU¦NÀÄ QæD‹¯ 5ÒȈlp¹Ý¨¢•D<Îá“>¼Qý-"#ÓŒÈ6S˜íDVø´3,:~kÇE‚ͧšxoW5®4+cÆ”0cz Y® ˜¬NŸªåßø1ÿýßÏð‹_ü‚ñã'rë­·²mÛ6>øà$I:Oê%“I Ãè~¸¿óïžž>àz„ µñÙ¿;iÔ$4ýó]¤6 æhçÜNE±»3»¾;P§ôöç~–L&q»ÝÜtÓM¡$|÷»ßá‰g¶rÕÕ˹ïÒRŠsdrKlļGQ´Rg%bÐK$®¢&5ŒDˆx8H8žÄ3˜™!ÐѤ°«JBU‚õQ<2—ܲ2’¡vÜjˆ¶NÂqò3œdefà3rHZ­<ûò!̲€%ᣴÀI¶g%UUû¹öÚ%üë¿}—µk¾É¢E‹ÈÎÎfëÖ­†ÑC–ôu¿yï µq¶N¿#%­ØunW 6Øïtž¢(dffrë­·’››Ë©S5¬\¹’úßþÊýLÏŒë8D<.ñ@ÏÈ¥­±K —Å¢'Ñõ$jDCUDtMÄ)Ê$…«G›^4ŒÍïÕ¢–Jdåé¸ò²1_££ÑjÆãràëŒñ×÷PëU±ÙȲ…4›DiVSÜ`ذI$’EüË?cÏ®Ý<òèã̘1‡ÃÁ¦M›PUµOM>X[_¨/4Mü¹$þE‘SM&ÉÊÊbåÊ•dffòá‡ïsó-+È~ ?øê5ÈÁ*¼íA¬jAK’Ô­´75ÑÚêglšF, +Ç.éh†„“hQ“°âÕU!Ûbåü l}ûÅÃuv¾ö6™ÚB:-Þ8ûê*›: )tA$+ǃE–ñÜŽ´ú-䦉8MÍh&7c§|…¿nZÏÉ“syþ… Œ7QÙ°aC¿äÿ<#EüsHïv»Y±b™™™¼ùæ6¾tÓ .½üî™w F Š¤ Ó¡0׊hXÄ$’ÍE^š@ÐÛNØâ@i‹£(q’˜‰†ãøðak_8Œ¢%°™Z™:ÝÅÒkrðvU úct„“:&ÙNB—0"²¡‘—nE7 ò²Ó9Y  (ÔuH¤‹dImüF޹žÃåï2wîµlÞ¼…1cÆpã7²~ýú´7~ŠøŸqhš†ÝngÙ²edggóÞ{ï²ô†åÌž÷M–Í*%]?….Ç1I:¾¶8ržˆd6ôC<‘Àï rª)FSkAÐI3‹´ý´:âšÉL ”@–­äå¸0¢n³WDÁž'Ë “Î3GNûLØLŠ.ðù‰)*¥e…å¹8íӌё(-*`rQ.§ëƒX]‹9~ðU®[²ˆmolgܸq,^¼˜—_~ù¢”)â_±…®ë\wÝu >œêªãÜ𥛹ì껹ù²R²“Õ$¥$²¨SÍxr2iôú©hôÑÚÁÛ¤½3B4'–TqȺ!ã+ÈÎL,›ÃÛÛÞ&ÚÅð’|FxuÇ›Ãdfdc5dAÂb– ‡4*Ž6 ›Âa—ÍW‰+:ÅEYdz,Ô4…Hĝ帡ÍÜrËͼöÚ6¦OŸŽ×ëeÇŽ©ŽNÿ|‰3kÖ,&MšD8âÆ›–Q4ö¾¾ð2 ’'P´¡Hœp$‚.šh Å©«k£-¨ÒÐÞ‰?ªP5 Å@0üº†$ ‚Qhni#=ÝMV^NO:ÁU¶£i*Ù¹™„=’0B^¯ª#CÒ‹;݉Ž@4jÓ ¬hª@ºÃJTÑ­fÚÛ}`è\6s,ÑwrÊdø¸¹|°ëY¾ù͵üñO2wî\jkkÿ!禈KœÌÌLæÎ À}ßXCk ‹_¯YJ™|‚6Í ÅŸ ½ÍO»ß Õ×L$&KŒDÑ’"šÉ†ÉfÁ™eÅßáÅåta*A_=–àà¾ãL˜4’,•°·§ÝŽ+? ³®‹‡‹ža™¼´§SÞ$9Y."A…6oQ–1Ûd,fn‡CбÚÌÄZÃXdѨÂéZ/—ÎGqqûÊ}ȲHÙ¤/ñç'ž`ÖåWpÏW¿Îu×]×ï{mSÄÿœáì=å7üåžynßûþoñ¨§ø ¦ƒŽ˜FKs˜“5>bªŽdNbÁJ‹?„;ÝÃåWLÇç kde»ñûCŒž8†ÎÇUø¸üªKÈH“ÉtÛ YZ[‘1ŠF»/AUMÍÞ -IL&+V‡³`ati>m!≢$¡ètD#df8hh àJw°ùµ}DcaF—æ’—ë ©)è® J‡Ïà»÷«¯žCÙè²îûMá¢Ëãë®R:W›Ÿ=9ÒõNªÁL®tÍXv}^¯—oÝÿ=æ.ý6™æïóSw:HØ×A4A³[^’EA~&û>ØÇÜë®fXÑâI•¦ö †`phïqœé´£ÕD“ÃJ ?æ2dA§¡ö5U5X%ƒN”ŒaIFàÄÉcT׆ˆÆ5l²@šÛICS;N§ÛLK]Ḏ?’D4Àj6¸rö%|åë·²î?_`ÏîJ\lD7ÌhI…p(¸€ó IDATŠÕf%½`míGùÖ}ÿ‹­[_ëqß]¥ƒyÁó…Ú¸ëA»G™‹†ø±˜ŽÍæ ''gÈõø]“••5è’MÓHOOgúß^Uÿ£þÝ\ƈ¢q9~ˆ`[oG ]ƒ¬aŒ_L^¶ƒÎ¶V®üâå”–ÒÐâåxyéééŒ?–p8ÎÎw?dû¶]ØlfæÌÇÒ›¿Êη÷ðÆžeæ´Rì6!»Ž YèÔ¶Æñdf“%8]VfϹœS§›©8rœi3ÊÈÌÌÄ2füx²r²¨«mätUJ2Ⱥ†·ÞØÇ‹ߢ¾9Â[;«ð¸møC6%‰MvS6z¯¾º•ç_xžå·,ïÞÄ6##ƒÌÌÌA—, ÕÆgßét’Ô“Ÿkâ?Ö×Ë.“¦MštùÌI“.¿àÆ»6c Ž?N,£¬¬ŒãÇxæé \ý¥_R[UM¬ÍžˆP6~$íí­8ÒÓ©>^·ÉÊ©šS¬ýöM=VËÛoìÆn•¸ù¶;ÈÈÉÁ$ÃðQ¹xï6IÂi5c 1bT‹ÏUeDa>£Ç™éÇÉÈÎÈ‘M¼ñÖA®ž3“¶¦45Ì%—”r`÷aÚÚCŒ)A¦GDÖ:¨«idìøIèjg˜ÜÙyL˜4’ãGs‘íí8œéøC1¬Æ–0’˃lËeXæ(þí_ÿ/K¯_Ê5×\ÿøEV¬X1ä·¸ÕÆ]ðµú¨5jë€-ŸWâ¯íëÀõ×»þÙçóÍôûýCªíÂÂBàÌ»¥[²Çùæ7¿ ÀOþÿ'䈆oc’§¸8Y2ˆ'%âÍ톆`6¸|îeTUµQu¢Ù§M¢ît¯oÝAþ°rrr™:¥ •ÑcJˆ‡ÂŒ,… ›™0u é. f»¯íbïì+oàŠ«¦ríó…"¼¶á¼{)QH{cŠRFcc;ïíª¤át °™ÒñÃxà_FIÄ ý´¶ù¸úêéèj’¿lÚCnž›YÓFp°Ò‹Éâ"oøDö|‰ÿ~ú)¾z÷=üÓ?ýµµµÝzŸ„϶u8fJÖ”CýõÿçYã[wïÞÍŽ;zì½8˜aøÛßþ6O=õÔ †áD"ÁÔ©S™ICC ­^ù'::Lž>“,?©„†Ú/¾´“@];—LÈç¿z“d¢dÔpÞÛ¾‹Ó íìØ¶›Ë… èÈÄ£1D*Ž5Pª§C&¦¨4¶ø¹yÙ5|õžqüדÛÙñÞQl&.’Ù9£9zt+ÛÞØÆâE‹™6mÛ¶mñ‡jã>ƒcÏçbó‡*èË<ŸÚ5èºNvv6EEEÄãq^ذ‰ÜÒUäæX˜ñ…é¼þúAZ[ü¨‚‰p$„h1ð¶w›áäGü'ö4˜ý5„cQÒÜv*˱ß1œN;ÃÚÛùð£L˜4‘‚aÙœmåG9þú6¦_q%Þ„Jk«1çíå X8UÕ@MÝ>tÑ@%$”D—K"Ó–ŽËe'!Ë$‹€žˆq²ÎË+[wrÓÒÙ俨Á4œ†úVŒd“dÃlËÅ"¦ñÄdñ¢ÅŒ7Ž;w¢ëú'¾uIwÿ>Ôç±xà³Kü3lÿp⫪Jqq1’$ñ曯S×£ô °Ø^xq7ª'©ÆP45©(‚HAi¢’Îîóä^Å"&YxÝex2ÜhÈè˜ð¶û±9¬””fær f§Ñ\Û@GG®¬RDN-' Óé ã°èŒ)Îä ³'"ÈV^Úôõ§NS2fþ¨J8C6‹HZ‚ì +Áp»Ó†Ùi!©©øƒQ†gd‘ÔÏÌêz½š›Ú˜xÉH&ˆ2µ5Ù¼õÎ!œN6—•œôBÞÜö&mmmäää››K}}ýïÂ6äþ·Íø|J¹skú”:†1eÈíuíÖUvk6›ÌK‹¢HII ›6nÀ™9Ž1e(¡V:ý1l’Šd•X¼xÛ_ßEKkŠªb±Ù-6\iÙ¨z’6_;þ0‡  #†¤úx V‹„$™ˆk!Îý§+kHFcxÜ6,2ì=RI$¡‘Wņç_惽5ܼlÇ7e¦MÅ5_œŽ·õ¯:E‹…H³§c2Vg’ú/u ¤9í¸ÜÒÝ6"¡f  ËÅ®ãI¯aDéHæÏ›E]cG*ÚÉpšqgäP_UÁ›Ûßä¶·QTTDmmíÇnã>ûÿäÜ‹FꈟþÏõõ74H’D$á÷¿ÿ=ÇŽãØ±cüþ÷¿'÷Y®ë:‡ƒaÆ¡éo½õ.“§]K  ¶¶ÛFF¦ÌÜySiij£­Í‡'ÝŨ²|L6+{wUàô8±9lhÈ:B\~Y+ïº‰Ž¶VŠFeãp8ñû‚¤¹]¨ñ²)ÁÔ+¦ò…y‹±»ì„ÂQ¢q…•§8U]G‡6½¸‡¦æf Ò æ\;òÃÕ˜LWÍ*Ã"‰äå:q§Û‰ÅB˜D6«‡â«¨ÃŠáqYUœC £¾!Dc]+š¡2ûòFd›HªIlV;2ðꫯg¶#ï/ƒ&Iápø<G"‘ «ñÿøºÿñûE|(•³‚  ( x½^¼^/ýîd¬ë:ééé8NVWÓÜâcâ”éÔœl£µ5ÈÍ·ÍaÑ ×‚a¡¥5ŒÃa¥°0—œìLªžäÖ;ç1wÉÕøýL_ÈĉÃÈVĶõ›‰…ýH¨4Ô'âkÅj’É6Wz—Ï]„h‚÷ß|‹D8A"!P]ÛIUŠ®ˆtz½L›ÆéýÛ8}`IM ¹-Äå—Oaî¢9ø ê|ø;DÝ„ÅbCÀÐ5’ªNFvee8ZGS{Í0pØ*de:(E$³‡ W»>üMÓÈÏÏï·~çBlÜ»5.ªZ‰O;ÅtO_LζX†6vÿÜ­·ûÌ)iZ÷Ìå¾½{p¥yˆj&$É`Ĉ<Ž:ÍË›ÞÃb3!I0jÌ8$)ÉþýÕ”y°»=l~á-DÁ†ÉdBéôòþïâ„èlj Æ BGc3•û÷3¼¨²ÉW ùàÕWÙ½c7²;—€’$ÐÃ$‹«´7z ´·óÚko±ŒÀb·³w_9óæÍâè‘ã4µtRRRH(%’T5M×ð¤[H„¼öv9&$CĈ“æq#É¡h ›Ã†Óaǯ€-mµ§ŽPWWGqq1iii´¶¶öéù‡jãÞ`Ì4jÅýõ?ðûÏ2ñ××£,]úÉ@]ï¥:xð&GQE#™L`µØÙ¿·ŠKgsךPâIì>H§×Ëe—gïûå¬ÿókTWÕcµË[;ñ&;©:°‡Ü,iùŒ‘‡„Ž,¦SVZÌárª¼‚êCG©©ª%à¡&ýtÄœdfzÈ+pÐÜÅÛRO[]6›Á–É]U ž‡ÙœÇ£¿yEƒá…”Á±cÕ´zC8lD¢QEá2Ëdçf¢b‹h¼ýÎatà³½ I¶R4,ƒ'[eq%IEeÅÅÅx<š››?Q›Û‡”CûëÿÏ4ñ33ÙÛÑA¯¡½Å"ðI/¡ûݲ'«O`ÈyTôÑZã%Þ)pÍÕcˆÆÃ¸Ó,“a²Òlœ<ægÛ+ïÒÖæG7DÐãÄ#ªÃ*“óì”8,7gBõ~ ,fLY¹˜Œ$MGŽ †9pì8íþiY¹h²‰ˆ ¢É„ŽDk[+³†Ùˆ"Y)—ªý§¨:q-&--í_e``ê‡jfÌA…Ow­À§JüK/eîáÃ<ÕÔÄõƒ‘0zN×»^{Ëè8àÌÔ»d™E$¢#I"f 4µx±˜À±åùmìú ’ÃGN‚E&Ã)!#€ÉFѨLì­(8“U$뉲5‰É,q’f±õ4•Uu$³lbäˆaœ<Õ„¨Ë´·H$|èšNÌH¢éJúè£}Ú¸7›Ï½|.k–­éñ¹Œì»…[¾äÀñŽúHø|ªÄÅtL& 2e Ë ƒõ--gȯ(I&OžÂÊ•+ù©‘e™ââbî»ï¾î­òúbÓÓÓI$hŠBn¦…`Ò@K@$EMÄPbaN×îÄn68|¤–ï}óaêOwˆ†Q’qtA¡©¦‰Ì,MõÍ´5¶s˵ù|þÚNA ¶bKŠíi|qá4ÃÂ#?û5±`Éd6†Cg؈lêkÛIÄ4 A@¬² M×Ñ è …é $±ÚlÈf õ­qš›0›B‘3¯šÁ”HŒçþü2iyŒ,."–Péè<³ˆ]K&pÛdÆŒEyùIlf M‰"Û­$ãg:½k“ÜÑ£G÷ió³m\TT4hŸ‹Q¹£Hª¯Ç·c÷­fõ Ú»:ú?¤VÿS%þ/~Q̃Öb iDfÎäÆx¾¾žëEQ ‹õØþz°3·]3‰@EQúõDÉdI’% CS‰ã´žnÅ$ØÓìädÙ˜4¡÷Þ-'ØáÅ/žYq$K"‰„‰êÊVdQG’tV›U¢¼>ÊÍ·ÄSœGòtG+ vú¨=v„æú´Ì\bQÑX—K"/7ƒÖ6?ICÇj·!ˆ"± ]$•DÜ`È`2cjB 'ÍÌñCû¹ý«+èl÷s´üY™všB˜et %Nfv:§k›8ÝÐŒE6“žf'U%]0ÍgÊâñx¿6¿Ÿ‹ˆ3ÒýX°´\Ïõ7”Rº{/{ïÿÙ&¾Ë%pÅ™ 55àõ’8~œÈÝh¢±ño¿ýö ÷¶O$´µµ±råJAàÉ'Ÿì^ÁÕÛ¤ŒaFމÅn£³½AÊÁbµ‘tÜ.™°?€ÃfÆ=ÜMZVµ'šqØ4Q¢£=@"@×5”d#!r²9Æ>ñËfbV£ä.¼ŽÈƒ´¬Drç‘“_ÈÑFƒôüB$_˜]ï—c6»e‰DB#"K"&Á†! 8]š¦“ž“MggI”ˆ$U4-A4.P_ׯO~ð+îºçÊ&óÄã/0óòÉ\yÍT>ÜyA—hn÷sºÕ‡®¸.’IC4ÀÐÐ —ëÌÞþuuu}Úülw½H{ ÷fsÓe&fKg^q¬¢ª[Øx…Wè’8õBýg›ø>XÃôé"N'ˆ"òÎk&Ö?ý‰'þ¤ïBgg'“&O!l%/˂ϟ$ŒŸŸF~Ai.ü¾éî4Üi¢!…ÿË:^zþeš[Ú9UÓÂÑŠÞ~óÑx%a 't¨:ä++ÿ…'Ö=C$£ªòmávD«™+¯œÁwÞÌÓ'S•Å KçsåìiØÓݘlfZÚZ˜2¥Œ‘…ù´6·‹*ªˆ,‹å“í–‘%…k®ž‚`œy›J8¢³ÕOùþÃÌ_zñ„ŠIÔHÄbdg¤! "ɤ†Y–DˆEýXMãÆë&þ?pÿúBà`ÊgÚãoÝÊæš¾ø²²(Š´··0uút’j”h¤“ΩêzÆOÆØI#©:vš4§HšÃNzÙW}‘ÓUµ<ûûWXyG‚úÎN‡Œ†H8n iN»•ÌlÏ™T©ÉÁ±Šz*Ž$¯0‡;înåšë‘îF4%âó’ž•&É$5[¾| ˾|#öæƒwv³÷½½üÙ€„(ˆ&IUC Œb·Š\2¾€ÇÉÈÉœ—Áæ[øÖÿ¹—ƒ†ÓP×N,Æîб[LŒ(Ì ÍiÃÛÐL°£žüÂJF•ÇÿÑÄö7Ï_ø™õøý‘>‘0êˆÛ•RëÚéìÏúšííììDQÆŒOv^ÇË÷0oîd&ωÝ)“S˜ƒ'ÓM,#©E)–An–‡‰SJ¸ûÞÛ9¼ï$æx;í ´4Ôa(>BþvÌf»#ô¼ fÍ,㦛¯aÚÌÉTkà×?þo~óï¿âð¾ˆE‚„¢¨ZâLŽ\’D³lÁí¶RT”®jdeä"ˆ Ž?Œ¨EU2œ+¯šÉ¥—MbæœY,X4ƒ„§hT>AE³dþLtM%¦ëDÃ:N«I]3ˆÅâ„"íLž:«ÕJ[[‘H¤_âÕÆ½¦D`’ªà³®ñ¿Þ×o´/õ†Bz‹Å´iÓÈËË`Ú´iX,–>;FEÂá0MMM1ûª+yïýí\¹d9¶t'ã&” ‘$ ‘9<‹Q£òØñên6þå \ž 3¹YvÀÁز|*+Oøp¸]B‚‘#3Hsۈǣ„A†åf2~R ¾Î zäYB^¾ôå›É5 ‡É†$1E™No/=ÿ áh‚‚Âaœ<ñ>ÙYé,X²[+›€ª'),NG«Eáñ8ˆ'’äçes¼²–ƦVf_y…/¿M{@!®×0û’v%™ ¨‡˜7o~w*s ·¦ ÕÆ½N\1fšg–ÿÉE‚‹¦,yêT9íõ×A¿k?ûU«VQVvf_ÈU«V‡ûÝLUUN:EQQ7ݸŒçŸ¿“ÎN/Ѹ( d¸%BáNÊŸdÆÆ3zLV²ò³ñ¶¶sººŽÖº–,¹œ??õÙ—¼¸þ}f_=…Å_ùédzé¿ÿ‚Õ"’UÏ%Ó®@¶ÚHŠgVáI‚@k{[_z…`êNÔpìð1â1§ÃE^–«”Y’Ç"X”$>_„šêz<„Âìn;éYnœ&ÍHRTR@øX;j"‚@\M¢Eâá6œV óœ©9}út¿Þ^Ó4Çm|^BÃP& ;EÿeÉŸí¬N?°^ˆÔ‰ÇãÝÇœJ—$‰“'O2gÎæ/XHFº{ßG´ü¿öÎ;ºŠóÌÿŸ™ÛÔ¯$PAH`À¦Ç`‚%z1’¤ØIœdgc¯7þÝØ@ÖÉnœ³&;qlŠ ˜2 °1½ Ñ õvÕn¯3¿?䙕É7Ž}¿çÜÃAwî{ï¼óg¾OyŸ7…S'Î1wþdú'röô úÆD2ú|Tp‹¥…¡©‰¤¤&ò£'—søP1Gö~ÈÄ pôЂõñ?z»ÓŒ¥ÝÂË%ÌÿáRb´‰4ÔÔ1yÚtz<ȸ ^Î>M{{+"ÝŽîGÀC@€§ÓÊÅâ :˜¢g™4k:iÃÓpØÔ•—£‘¬Ü7rý%ÓÜj#(4«ÙÆÀÁITÕXð™´ ’WÂîôÒÒRÉ„‰ã”’‚Åb¡¶¶ö3 ïfŽo'uî1®}Õ?FºÃ¿ñÐh4ÔÕÕQWWG¿~ý˜;w[òßgôwWñÁ¶ÓŒÓg=HxD[÷±éï;3†¢4\»zƒKE—8qàÏÿá9Rï[Á'…{IMÈ¡=û‘}^²æLãñ„GÅp`÷~ÆOOê€tÃÓñNÀél§µ©‰ÃŸåZÉ šÚ”šÀˆQé 2ˆ"‹ ¯OKp€IÖÐÚlÁl¶šžJRê`Žî=HòA<þ«•8œNJΗMll4¢(Ñ'‚ÔÔ8Nœ)Åîp® ÂmnÂé©!où¿-­Vk¯û”~a—ÿÛØW§°°»^ÿç@õ6¥X£ÎZÓív÷ªË—Ó館¨ˆììlV¬üoü}²·‘V«%ÕŒ™Î•‹ØÝ>¦NEKk+ÎV3adHåþ´ú,ùárÆLšÈàûZ(+)cäèÁDõ aHÆ"£bÙ¶iRûãr»ñzš›AòrîDW/_¡ìj=­-­$%Çðñ‡e+¦üz 6§LX¸ž>}û¬§¹ÍŒËëåÔÑb¦ å;³¦S_WKCU-v«“ôQÃAßÑvÄióNâÀDL-6®–T#ˆ^ìí¥$ÆõcÁ‚EõªëñÝÎqç–ò´(XØ-ñæáo.ñÏœqtKüéÓCˆ‰‰!==½^ßkâw®OKKëR¢Ü“vµZ­˜ÍfÆŒËôïLâü±÷™4ÿ—Xm.¬ n§¤ñÀÔ >8@`X±ÉᘚÛëÎÅ Õ¼²öUVÿïïqûl$ ê¨I˜€Ýa§áÒyY4`c8n»•ÚÚ:ÌVíÍMØí.ÂûÆèÕ`ЛDUÍ%ÀAßè(¬–´ˆn¹JæÈtúFÇj4r±ø ¢Fdï¶ùdïi~/›ä´ÁdŽŠìur¥¤ŠêòJ<ˆïASmŽÖZ­¥üüWÏc ãĉ¸\.FÌå. IDATŽù¥ÍqgŸÊh4"Ë2gœg¾Rçé§k»£"¿þu™™™dffÞõø‹/îõ±•••lذ•+Wòü ÿÎÔ©ßÁmËC Lùõj’’ã:Ö׆R]ÞÄôX¼š@šM58†`#Ú Vk3ñ ñ$öO $ÄC@°+ˈOJàÆJ"ú¶ÑR×N]M † ¢Y’IH‰ÇÜf!4"œˆˆ>ÂÂЄ€ÏM}e5ÆÐ \.«Í‹O2PzµŠA©qTUTóñ®#"¸xæ"‚ åFi5WÎ]!y@"mm-ܸVɃE´¶¹00¯Ú†ËÄF÷eå§»“¿òÊ+¬^½šÄÄÄ/uŽ477ãõzyºæéo§ÔÍg’±¦¦¦×½3•åˆ#8wî\¯*A ªªŠ?üá,Z´ˆ)S¦2{ÖLöð'Ò3Fà“è¡gРDìNZƒ–‡²'cªmÀã³×)ÓGÞ§>Ÿ‹¨~‘ƒ½Xm6üï{|gÆúö…Or£ á̉‹X¬$Ÿ‹¡CSˆˆ YÏ©c—Ðbmm¥¥®/}ãc¸oäBÃÊ©(»N»Å…©è’Gàúåz[êøxÏq¼²ŽÁC›ñy\Ÿ¾Lk» ‡[¢­± —¹“õ¿ûÍZ¢£¢9~ü8[·neÉ’%444ô*,y7s¬ÊúO°Œ3¦ãò;‰ß³ô¾pá"üQ¯ª3•ǰ^¯'-- €;wöªç‹V«¥µµ•ÒÒR8À¢E‹øÏÿü/FŽÎþíºx6.K3%WÊI>è„xö~XÄœ…SYø½Ù|¸í0e¥×ij4aµ´¡Óc0èh6µ Š‘9ŒÓŸ±á÷q{$†ŽLŸHl¿¢c¢9¸÷o¾º™¸~¡ 9‚äþQx%-§¯^%)1ƒ1 ½F‹àuNpP¥¥e8NDWÂíñ¢&©"MµMCC15›)½Ñ†×' Åã0SWu‰áéÃxò§? ´´‡ÃÁG}Dxxx¯vD¹›9î¬ñS¥ðÀ|«ûêüOwoìÚå¼?,LO@@À¿sßGƒÁ€(нÚE‰f(~HZ/ü¿xî__¤ßà‘„x1U™in4óÈ‚i˜šÚø`ó',\ö {„Íop|ßyúödòŒi$ ˆ£¾êkˆŒŒdÈðT† ر«a» 4ZBFŽ:Js³ABXl2nŸÛNeiQÃÈ ã5~4[^Ÿ gJ˜:k*U¥UTV5P_o¥ÅÚŠ^@JÚ’ûÇcÐa³8¸t±ŸOD’túô¢‡Ö†´º›x÷oôiëõέ C¯Û„ßéwžëP](—=—Götý'¿ÉÄ_ÙÝGޏ˜3竊¢¨:kO?³Š;wpà`îÖÐæjàåÕ[ùÑ?M%kîúö áFI5Q ÚŸ£ûràÃx½"ÆÈbb"Ð8rä >­ˆ¥ÙŠÓ#10- ŸO¤°à WÎ\&qP³M!*.N=‡¤Ó²à{óèÍ»ßJbr}'áu{ 3†Òƃ÷ßǘ1£ˆŒ Åíuã´Û).ºFc³‡Ó…F¯ÅèlâBÍ)~ù«§˜öÐ4,K·‹Ç¿lè=åÞò¤ž®ÿ7øÝâ«è«s;(‹-ÊËË?~<ë7¼Ã˜Qûð¯L_üsv¼»·þ·øø0bcƒyx΃_G 6>–½ùŸpù|)cǧ2(­?ÑýúðÝìim³ %a@ìN;ÛßÞÅ…3çx`òX@|R ­µ•MŒ“ưïÍÅbu°cën’bq;¼È‚D`h8--­hƒuLËþiCñÈ2n¯“à@N§LH¤£Ý mŒš`ðy¸~í(ã'Œå·/¾ôéuãÇÿZˆÿY}u¾íÿkƒ(Š’œœÌ€Øôî{|÷»3 ëãËs©¼|Ün¾H“©‰%¹39yô,CGöÇá´Ó/Öˆ€–úêB‚‰]¤ 臤éhåvº ç¾QLžñ>AK`x_ F7±‰ý±¶6s¹ø uMmŒ}pU×Ê8q¬Ÿ¨áû?YLCm= „äqÓÔÔN›ÙN§!44œˆ>}@Ô¢1„à³ÉT^ØKh˜ƒ·6n"À`àôéÓœ9s†)S¦ø/¶Ÿø]‰ïv»Ùºu+ßÿþ÷™1㻼þúk<ñÄÐhd¦ÏYDh‹ÉßJÑ‘Ó4·Ô3möxΜ*aòÌÑÄ'Å28m·„ÛíY µÍŒÃåÀŽÛmæ‘…SAÔ!yeÐ¸Ý ¾mVV‡˜¤8úFpHíf+LG@H8ndÐvÔä{}nê›mÈbGO»Û‡×jÃbq€×K  %WŽcu\'÷Nú'w´ßµkZ­öëZpâ'þ½ e ï–-[xì±Çxüñ'°Z­üâ¿Döx™6{ÞÒfD«×òÀÔ 6;ÁÁ¡H²Œ,zÑë5È>‰†ÚF :=¢ÐLp¸IÖâvZq¹œè Ø,6dYƒÛå !1 ­.$ >2GñÑb¯‹¶–6‚BÄ%Çc·Ù2ºp9Ü MÔ6¶bn·`k2ckµsùâ)l¶Ë¼÷þ»Œ3ŽÆÆF¶lÙ‚×ëýºkîýÄ¿—a0(//gË–-,^¼˜þç_ÌÊ+±Y›™ðð|’’‚$™¶V}¢ Döí²„$Éhv— ¬'Ü‚Ùb£¥± 6}`0.­A$,<F‡¨éhÅçõúðz\¢†0c(c¦Œãò™s "ûôÁíñàvxp{¼”]¯ ¢²ŸOÀ+ihoñQv¡A[˶ü0aÂ$L&ï¼ó‹N׫…áߪ§¼ n%II ›7oÆårñÄ?bûöíÔ” ãSßà@C¸q£…ÚªÚLfv6«·ÇŽ!P‡. «ÕKk»…¸”AÄ$ BdDc0•D€!¨Eò¹|œ.;N—³£s™Û‰µ½ŸËBÊÄD‡ÓÞÞŽ¹Ý‰WÖâ“*kmXZ¼Þ LuN®í'!ÑÞÂ|&L˜DSS›6m¢¹¹ù+ÛæÇOüoù7mÚ„Åbaæ#päè1ú'ò?¿}’·}L“ÉÃŽ§9w¡‘Ê MZLV‡I1ûà–ôˆ:=’äÃewàrXÜn¼;·›µ‡ÍŒÇåÀÒÖ‚¹­ˉ¥ÝJ]uÕµ&vgGû§“ÆÁáèC©*©ââÉmÌ™?œ=ûv1dÈPªªªØ°a---½®yòK¯‚ ŠâUg*f'µ7É•Þ|‡Á`àÆ¼ùæ›Ì›7Aƒ±oÿÖ¼ô"/ÿî÷D''>y"…M—H¿ )Œô1°X}„…ƒÏ'ÓÚd!Є,J€A‘|dÙ‡O[Âçõ>tH¸œ>d$ÜN.‡ŒÇ+£Ñjih6SWßFmu Õm\IDFÂîðI>dÚÛH^ 5f®\¬¦ôÚUæË<–3…gVýš˜è~X,öíÛDZcÇEFs˹ÜÜïÒåru{Î_Äwþ^·ÛM0Á÷ñ¿âøÖ…nÌ‹—_NÃf[Å /üáŽGUzÞÛl¶;þì;ï¼Ã¬Y³8qâD·–I’$¼^/C‡%!!AýûÞ½…¼üòï8xà,1 #;5‹á™C ÑÇ‹Çc'<\GÁ€F¯E£ÑvmI¢F‹×íÂíòáðÈ’ Ã+{1Û<4·ú°Xøœ^š«k¸Pt«†¬™÷ó³ŸÿÃGdªÄ,))¡²²½^ßãþ_cÇŽ%??Ÿœœœ¯tŽgL˜ÁÓûžfúµv=Lÿj¹xÏÿ?þ#™‡:ÃÑ£§ïx¿R-¨Xnå"}–¬Òh4dee©ëIïGŽ⯯½ÆîÂO°¹t¤¤eÐÐ1DÅÅnÔ EÐj$;¢ AFÀçóàrÉÈ‚€,KXí:Z-NÌíëZi¯½A«©ŒH£—ì¬ ,[šÃ´û>×o½zõ*ø|¾^ͳÇãA§Ó©s|§²EbBbè“ׇ¬Y÷ ñï©£×ë9þ47n¼câ»\.dY&))‰)S¦°aÆÏ| ‚ vèׯ_¯¶·¿ùf‹eäÈ‘Œ?‘ñã'ÒÜlb×®vîȧhÿk´˜=EaŒŒ'<*–Ð`#a¡¡è‚‚¯OÂáò`·8hkkÁlnÁÚR‹ 9ˆ dêiÌœù3¦Lý;—477söìYÌfó‘ÐëõòðÃS__ϻᆱÎYOðù|ôïߟÊÊJµ•àÝ,U3l ‹—/æ^Â=C|ŸO&  c#áÞPY4qâDt:Ó¦M#;;»Ý·~ˆN§ëv‹§ÓɱcÇ0™L8Nž•‘#3’6QÔª7Ùõë×9wî×®]ÃjµÞqÖãñ0qâDL&ÇŽ#33Sݦ;c’™™Éo~óþøÇ?ª;›œ:uêŽV`ù|>BCB‘ù[Müñݽ±l™þûåå#~:îŽæ§st¥ó¾U³gÏ&22²K=¾òžÓédÈ!,\¸ÖÖV^yåõ˜W^yåŽëñ&d¬~ìQàWßVâíî„íäK—ÜêN½}|}J$‰ °k×.6lØ@^^‡£ ù;ß,wò=Ÿuã)ûAÕÔÔPYYÉÁƒ %22’¨¨("""'((ˆ€€µnÆ!¹himÃétb³Ùhmm¥¥¥“ÉDKK v»I’Ðjµêë‹‚ÓéÄápÜÕQH?gÎòóóùÉO~ÂË/¿Lnn.f³‹ÅrGíE<v—p1¼¹§ëÿmgêîäé­X¿… "Ë2‹-b×®]Lœ8‘I“&ððÃ+÷oGþ/#Ñ™œ6› ³ÙLYYÙ-9ŠÎ¿C–e$Iê²Ç”F£Aů<ëª~éÒ¥@GCÙÚÚZ–-[†(Š,Y²„¡C‡ræÌ™»yêhî!®}µÄÿ¢xçóùÐjµäææ’Àüùó)((`Ò¤Ilß¾ððpžzê)ŒF#Ï?ÿ<ì1Ü÷E£»ÄÍÍšZ¹a¾n(¤ÏÉÉaß¾}„††’——‡(Š,]ºTÝ'kÉ’%ȲLqqñ“_è!p##s‰_^žú…^£Ñ““CBB ,`çÎ]Hÿì³Ïò_ÿõ_DGG³bÅŠ[,ÿç…BÞ»¹‰¾ªïNäˆBúÜÜ\nܸÁôéÓ‰ŠŠ¢°°ÜÜŽŽî ùEQT;-Ü)ùˇ”wû^ús‰ÿë_×wä½÷’{MúÜÜ\™?þ-¤_µjk×®%..Žüü|¢££)))éBþž,lO™LAT‰¥Ôðßœöï,e£Èåÿ )‚ú7…¬·‹´ˆ¢Øå³J)ÁÍãu–ç3ëðo–7IIIüêW¿â÷¿ÿ=YYYäçç“››‹ äåå‘““ƒ,Ë<úè£wLþ_×ÿúÛ)u6onëŽn@r¯åMbb"óæÍ»-é׬YC¿~ý(((`äÈ‘lܸ‘ýèG¼ð <óÌ39rärI’DPPzA8‚‚‚Ðétœ?žºº:¦M›†Á`àÈ‘#ÄÅÅ‹ÏçC¯×wTrÖÔ Ì›7‹ÅÂæÍ›5j™™™ìÝ»—áÇãñx ÃáppêÔ)²³³q8„……©¤µÙl >þøcFM{{;’$ÁåË—7n$!!àà`öïßÏÌ™3‰ŽŽ¦ººš={öÜv;%d9dÈ–,YÂÖ­[yã7xë­·xùå—Ñh4üîw¿cÖ¬Y¨ÙÞ¼¼<õ)ðè£"Ë2gÏžíù7·n¾gÚ‹ˆ_ý×u÷꽦Ÿ;wn·¤‹‹SI¿iÓ&–/_ŽÓé$&&Fux'MštÛš ŸÏGUU±±±DGGSSSCrr2ÁÁÁc2™˜3g‡ƒòòr2331™L hllÄét~Z_ï%11‘ÄÄD† †Ñh$,,Œ„„† B\\aaa´¶¶’‘‘Áý÷ßOBB×®]£½½„„\.‡ƒ„„ôz=‰‰‰ÄÆÆCbb¢:Þ}÷ÝGLL ÑÑÑŒ;–aÆqîÜ9222HII¹m-¾Óé$55•¥K—¢Ó騴iùùùÌ;—¶¶6Ö®]˳Ï>Kmm-ÙÙÙœ={–œœ6nÜÀc=Æ»ï¾Ë¢E‹ÈÈÈè]”ìî.ÿ7>ªãí‰ôЦ¿#Û™ôùùùdddðöÛo³lÙ26mÚDNNG¥¨¨ˆ'Ÿ|’¶¶6þô§?u‘‡ƒüü|RRRðù|––†ÛÝf5›Í8Nâââ(--åàÁƒÔÔÔƸqãØµkV«N‡×ëeÀ€477@bb¢jy“““UésâÄ ÆŽKFF—.]bÿþýÔÔÔššÊ‰'p8¤§§3zôhôz½ºÇ¬ÇãQeT||?ýéO9xð ,`øðá],•’ÈêœÔ’$‰ÐÐP222èÓ§7n¤ªªŠéÓ§³hÑ"BBBT2éõzUó ’’’¨®®¦½½¢Ñhp»Ýèt:úôé@cc#W¯^E£Ñpúôi´Z­ZG¯ÓéT$::ú–ð¦òdñz½ÄÆÆ"I:§ÓɤI“X°`III]’T‡ƒQ£FñÄO°ÿ~FÍ“O>Ipp0[¶l!++‹C‡1{ölZ[[Y³f O?ý4µµµdeeQ\\Lnn.6l ''‡÷Þ{… vkùƒÄ NºNŽïéúÓ-þÄîÞ(+ó1dˆ¤:Œ’$HNN‰‰‰Ì;—‚‚&OžÌöíÛ1<û쳪#«È›·ß~›¥K—"7n$''‡³gÏ’••ECC«V­b̘1lß¾K—.}f”G§Ó©õ-Lš4‰£GÒÖÖÆý÷ßOXXØ--õ|>ÑÑÑF†ŠF£QË$µµµDEE©ˆM&­­­Ý¶ÿؽ{7Ó§OWû)Ä÷xø£ÑØ%z³sçÎ.¤¿ÙÒgggSWWǪU«x饗$ £ÑØmû¼Îä“$‰þýûóÔSOqùòeBBBxôÑGñz½TTTÐÒÒr‹%öù|$&&"Š"{÷î%::šûݎ ”••1räHõ{”•¸¿BÒÎ%J}çψ¢ˆÁ` ¢¢‚1cÆ Ñh())aðàÁÌœ9³Û¹¬¬Œææf233),,dæÌ™üùÏF^}õUuÍñÎ;™3gÛ·ogÍš5*ùwîÜy‹ì‘e™%K–0bÄõf“$ ­[ËÏ•{IéÜ;Ä—$ááÑ„‡÷íu¸]œ¾³¦ß¹s§ªéóòòºXz…ôµµµ<ûì³¼ôÒK*‰† vKBI!Ú?þñÕoÙ²­V‹(ŠX,ÚÚÚ0`’$QYY‰F£áøñã\ºt »Ý®f\+**X¿~= ®\¹¢Ö¸455±~ýzl6AAAS^^ŽÍfC¯×ÓØØÈúõë1™LH’Äúõëijjâý÷ßW”èt:¬V+555Ô××óÖ[oár¹hhhÀd2©>‹ÝnG§Óݲq[\\Ç~k)))2cÆ Ö­[‡,˪å_¸p¡jùo&¿¢ùk¿lÙ2{ì1A¸%2f7Ù¹è»è'þí`08{ö‡³dÉY¼x±jéwìØ¡ZzEÓ+¤çwn!½"oêêêxî¹çxñÅ9rägÏžÅ`0ÐÜÜÜÅÊvFee¥šQ­¨¨¸%{éÒ¥ŽÉÓjÑét˜L&Õ˜¹ 477ÓÔÔ„V«Åétrýúu5F¯Õj)--U­¸RŸ£Ü`N§“ÒÒRU"]¿~­V‹ÙlîÇW>«Õj)++Ssii©z#+¿Y!¾r¾o½õhµZ-ZÄž={˜1cÆ-–¿³ìÙ¶m[·äï,{ËèÐ!Nž<ɰþÃ0|×pOÿžZ éõº˜4iÁÁÁ*é'Ož| éMߥïLúU«Vñâ‹/røðavïÞMCCµµµÔ××÷¨ë§R§Ó©/…ˆŠr»Ýª÷ù|]–ó)N®²#ˆ(v,9TŽë<†×ëE«Õ"Ë2.—Kþ(ŸQ´²rŒ×ëU“hJéõí~³r#ÝõõõÔ××SZZÊo¼ATT{öìaРA¬[·Ž•+W¨:¼dΜ9´µµ±fÍžyæ™.¯ê”e™Ç{ŒwÞy‡‰'Ò·o_ï¹fV÷ŒÅw¹\Œ=·ÛÇ‚ TÒoÛ¶íÒ+–~Ó¦MjÈòfM_[[«júdzgÏ•$wRó;L˜0£Ñˆ \½z•ææfÆŽ‹V«ÅãñpöìYL&S§N¥_¿~œ?³Ù̈#Ôí5«ªªp:Œ5Ц¦&>ŒN§còäÉDDDpåÊ.\¸À°aÃ9r$&“‰C‡1tèPbcc9tèЭ}½åÂZP'&“‰¿þõ¯<þøãòðónÝ:€[,ÿìÙ³UÙ#kÖ¬é¢ù•$—Rç³lÙ2Ο8O¥§ÒOüîJ‡ÃÎâŹª¦ß¶m[·šþwÞQIßÙÒ߬éo&ýçsÀ;¶±cÇŒÙl&33“={öðàƒÒØØˆÑhTQS¦L¡¬¬ŒE‹qüøq DDDN§“°°0HEE“'O $$„‘#GR]]­êä… R^^ΤI“£ÑÈðáÃ9~üøÕÅ÷½^Oss3ûÛßøÁ~p[ò¿ÿþûªæŸ;w.Û·oïçW¢= ù—.]Ú%ÚSm­îØêìÛ™¹í¡¡¡üùÏ¿gçÎLœ8ñ¶eY¥ZP±ôм©­­å¹çžcõêÕ_(éoŽÚÔÔÔpâÄ t:F£€“'ORVVFPPéééÔÖÖRXXˆ ´´´°~ýz>úè#JKKÑëõìÞ½›¦¦&RRRHHH ¾¾ž;wrîÜ9RSSÑh4ìÞ½›ÆÆF’’’ðù|ªƒûEB¯×c2™xã7èÛ·/……… <˜uëÖñä“OÀûï¿ÿ™²GÉð*çš››ËÎ-; ü´?¿_ãßA¨¨¨$33“üüüÛ–!(òF‰ÓoذÜÜÜ[H¯hú/ƒô¾ˆ—¤¤$f̘Amm-µµµÈ²ÌC=DZZÅÅÅêbnEcw.S’cŠŽW^å8¥ýŸÙlîrÌÍp_ùÿö·¿uÑü¯¾ú*+W®¼…ü“\JyƒB~%É%I/ýö%dŸ|ÏXû{Šø‡3!??¿GGvùòåª#ûu^‘emmmlذ7ÞxC]{øðaÊÊÊ6l˜êX*ä¼Y–(W­¬†òz½DGGó/ÿò/$''w9F)WPn†/ãÜ:Ë¥4¹³ÃÐ%Ã;gÎZ[[Y½zõ-–?77—×_àà`<^ßâßžøN²²§–!Ü.9%ËrGV!}çèÍ—Iz€€€$I¢´´´K×2»ÝŽËå"$$„òòrâââÈÊÊ ¶¶Vµêz½^ “Κ5‹¾}ûRVVÆ7ˆgÖ¬Y„‡‡sáÂdY&;;›¨¨(***Ôþüc–/_®Öü|Y–_‘=·‹ödggsèÐ!æÎ«Zþ›£=?þ8¯¬{—ÛuOÿžqnIÐ]‚²¼™ô3²_6é•1<¨–!K’Dkk+û÷ï§¾¾‡ÃAee%ÅÅÅ444Я_?ŠŠŠ¨««#,,Œýû÷SUUEUUëׯ'55•òòrNž<©ZþÐÐPÕhiiQ9qâÉÉÉ´··£Õjq8Gôa ›IDATw´àänÉÿÃþ]»vñÈ#tqx7oÞ¬fx‡÷æ ¯r Ë­å÷”s{Ï?88”·ßþË-¤W4½(Š]Ù›Ël6Ÿ|òÉW²ÂÑ£G½¾£J{{;ûöíC§ÓQ[[‹$IèõzN:¥Þ0z½›Íƾ}ûÔB´ÒÒR®]»¦M–eöï߯‚ÛSRRÂÅ‹oñ¾ (²gýúõ¬X±‚?ü™3gÞ6ÔÙSyÃÞü½èï»·Ø~ÕR§½»Wp°ÆyìØq"""ºÈ›eË–u±ô5½BzÅáìœ`ú2a0ºt"E‘€€4 :N­ð4 ]ŽAí² $š”c”,«òE>Ý|ŒV«Uw†ü2Iß9™×ÚÚJ[[)))]’\+V¬ €Í›7wqx;G{jjjx꟟B/è½Ý_ÿo:ñw÷züqÝ'%¥ñÁtÉÈÞœœzä‘Gºþøñã˜Íæ¯eS³o ”â8—ËEll¬Jþ?ÿùϬX±¢K†÷æ’æŸýìgT˜*§÷IO×ÿ›.ušº{#0ó/ù+DQä¯ý+O<ñ¢(ªÉ©#GŽM[[›šœúøãùøã6l˜Z¢Û›l¦R* Ää•Ò‚o2”Ø¿R³ãv»ÕrŠžÐ9Šät:ùûßÿβeËØµk—ZÕép8øË_þÂ{ï½ÇâÅ‹ùðÙ>}:Û·oç•W^aóæÍh|WO×ÿN|¹§¿Š¢Hqq1o¾ù&ãÆã_~óeear8øï×^cpFsfÏæù§ž¢¼±‘Òº:R331„†0 =Ÿ×«ö1‘,–n-Xdd$ã§=LLÿ„G’’>âŽË$›¥çÇ©&ôsÍ–äë~üPñÎÆöz½DD20f ˜AƒÖ-ñCõ•øZ­–à@ §_¤‘ÓGòð¬¹þ¨+VP~å"Û6¿Íâ¼ïQ°ýülåJ,âü#«W¯VVhquüJ}츸’nÞñQS34X®løöïOt8éJw^îÓh†²:K)ë š¦ó ]œ1£ª§ßã:¾ï>Ÿ×›¤ô…¼S7püŒ¶C´–=w1U^à“ÀÐîîØãØ£¦½ö•b9­Fkžf˜v¸§c÷ܰ„ÒiሯS·Ÿ¯£¿¿! @­¹w8hµZ4 z½¾fj?ý9¥ħOàSñ%ñ¿éŽqµCj¿¹Ä·Z}=Dunu7Î Â÷7„Û<„Û¨…Γ.tÖEÊû“Ÿàñ›Ç´IÝ÷ÖÑ„|s¥ÎíÈýž·¾7wk7¤ý̇Ðqþwy÷f? Aì!x Í|_ù"ä¶þÝœb`ïÁdÝñOî ¨¡Fè‘WÁâ½³+Šó·¯C]jÓŸD;ù×èçÏG­óÏÉ7Þ¹ýŒÈCe%¶C‡¾0ýÕ¾i“ú¤yè!|ö犾 ^¬í;7©ò!tê<œœÆí¨ê¹G¨6c%bÔ:ð€ )F¿8 ÷»;‘mq·ŒßÜ1¾¨ &8<‹|ën¬’ù ùí›Ú7©ò'NË8ÝCl»Þ‚$}1Åd›.5êÈ ‰ btlŸøÝÁ~èyyêcèó>Ž*?ÍH@JÁ.UÇ©yæ4aŸŽÿ9eeåʼŽÁ5öQ9M¬¡­¦Q£Dun‘7O"&¯ ²ìC$ Åè0 ÷ù7“¿²$Y†€€H’3JùqÝOhpÖvósNN^eÊäL‰|MaGÈÝz `_ in Python. tappy generates TAP output from your ``unittest`` test cases. You can use the TAP output files with a tool like the `Jenkins TAP plugin `_ or any other TAP consumer. tappy also provides a ``tappy`` command line tool as a TAP consumer. This tool can read TAP files and display the results like a normal Python test runner. tappy provides other TAP consumers via Python APIs for programmatic access to TAP files. For the curious: tappy sounds like "happy." Installation ------------ tappy is available for download from `PyPI `_. tappy is currently supported on Python 3.6, 3.7, 3.8, 3.9, 3.10, and PyPy. It is continuously tested on Linux, OS X, and Windows. .. code-block:: console $ pip install tap.py TAP version 13 brings support for YAML blocks for `YAML blocks `_ associated with test results. To work with version 13, install the optional dependencies. Learn more about YAML support in the :ref:`tap-version-13` section. .. code-block:: console $ pip install tap.py[yaml] Quickstart ---------- tappy can run like the built-in ``unittest`` discovery runner. .. code-block:: console $ python -m tap This should be enough to run a unittest-based test suite and output TAP to the console. Documentation ------------- .. toctree:: :maxdepth: 2 producers consumers highlighter contributing alternatives releases ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1429298097.0 tap.py-3.1/docs/make.bat0000644000076500000240000001505300000000000013762 0ustar00mattstaff@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\tappy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\tappy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1576375629.0 tap.py-3.1/docs/producers.rst0000644000076500000240000001740100000000000015114 0ustar00mattstaffTAP Producers ============= tappy integrates with ``unittest`` based test cases to produce TAP output. The producers come in three varieties: support with only the standard library, support for `nose `_, and support for `pytest `_. * ``TAPTestRunner`` - This subclass of ``unittest.TextTestRunner`` provides all the functionality of ``TextTestRunner`` and generates TAP files or streams. * tappy for **nose** - tappy provides a plugin (simply called ``TAP``) for the **nose** testing tool. * tappy for **pytest** - tappy provides a plugin called ``tap`` for the **pytest** testing tool. * tappy as the test runner - tappy can run like ``python -m unittest``. Run your test suite with ``python -m tap``. By default, the producers will create one TAP file for each ``TestCase`` executed by the test suite. The files will use the name of the test case class with a ``.tap`` extension. For example: .. code-block:: python class TestFoo(unittest.TestCase): def test_identity(self): """Test numeric equality as an example.""" self.assertTrue(1 == 1) The class will create a file named ``TestFoo.tap`` containing the following. .. code-block:: tap # TAP results for TestFoo ok 1 - Test numeric equality as an example. 1..1 The producers also have streaming modes which bypass the default runner output and write TAP to the output stream instead of files. This is useful for piping output directly to tools that read TAP natively. .. code-block:: tap $ nosetests --with-tap --tap-stream tap.tests.test_parser # TAP results for TestParser ok 1 - test_after_hash_is_not_description (tap.tests.test_parser.TestParser) ok 2 - The parser extracts a bail out line. ok 3 - The parser extracts a diagnostic line. ok 4 - The TAP spec dictates that anything less than 13 is an error. ok 5 - test_finds_description (tap.tests.test_parser.TestParser) ok 6 - The parser extracts a not ok line. ok 7 - The parser extracts a test number. ok 8 - The parser extracts an ok line. ok 9 - The parser extracts a plan line. ok 10 - The parser extracts a plan line containing a SKIP. 1..10 .. image:: images/stream.gif Examples -------- The ``TAPTestRunner`` works like the ``TextTestRunner``. To use the runner, load test cases using the ``TestLoader`` and pass the tests to the run method. The sample below is the test runner used with tappy's own tests. .. literalinclude:: ../tap/tests/run.py :lines: 3- Running tappy with **nose** is as straightforward as enabling the plugin when calling ``nosetests``. .. code-block:: console $ nosetests --with-tap ............... ---------------------------------------------------------------------- Ran 15 tests in 0.020s OK The **pytest** plugin is automatically activated for **pytest** when tappy is installed. Because it is automatically activated, **pytest** users should specify an output style. .. code-block:: console $ py.test --tap-files =========================== test session starts ============================ platform linux2 -- Python 2.7.6 -- py-1.4.30 -- pytest-2.7.2 rootdir: /home/matt/tappy, inifile: plugins: tap.py collected 94 items tests/test_adapter.py ..... tests/test_directive.py ...... tests/test_line.py ...... tests/test_loader.py ...... tests/test_main.py . tests/test_nose_plugin.py ...... tests/test_parser.py ................ tests/test_pytest_plugin.py ......... tests/test_result.py ....... tests/test_rules.py ........ tests/test_runner.py ....... tests/test_tracker.py ................. ======================== 94 passed in 0.24 seconds ========================= The configuration options for each TAP tool are listed in the following sections. TAPTestRunner ------------- You can configure the ``TAPTestRunner`` from a set of class or instance methods. * ``set_stream`` - Enable streaming mode to send TAP output directly to the output stream. Use the ``set_stream`` instance method. .. code-block:: python runner = TAPTestRunner() runner.set_stream(True) * ``set_outdir`` - The ``TAPTestRunner`` gives the user the ability to set the output directory. Use the ``set_outdir`` class method. .. code-block:: python TAPTestRunner.set_outdir('/my/output/directory') * ``set_combined`` - TAP results can be directed into a single output file. Use the ``set_combined`` class method to store the results in ``testresults.tap``. .. code-block:: python TAPTestRunner.set_combined(True) * ``set_format`` - Use the ``set_format`` class method to change the format of result lines. ``{method_name}`` and ``{short_description}`` are available options. .. code-block:: python TAPTestRunner.set_format('{method_name}: {short_description}') * ``set_header`` - Turn off or on the test case header output. The default is ``True`` (ie, the header is displayed.) Use the ``set_header`` instance method. .. code-block:: python runner = TAPTestRunner() runner.set_header(False) nose TAP Plugin --------------- .. note:: To use this plugin, install it with ``pip install nose-tap``. The **nose** TAP plugin is configured from command line flags. * ``--with-tap`` - This flag is required to enable the plugin. * ``--tap-stream`` - Enable streaming mode to send TAP output directly to the output stream. * ``--tap-combined`` - Store test results in a single output file in ``testresults.tap``. * ``--tap-outdir`` - The **nose** TAP plugin also supports an optional output directory when you don't want to store the ``.tap`` files wherever you executed ``nosetests``. Use ``--tap-outdir`` followed by a directory path to store the files in a different place. The directory will be created if it does not exist. * ``--tap-format`` - Provide a different format for the result lines. ``{method_name}`` and ``{short_description}`` are available options. For example, ``'{method_name}: {short_description}'``. pytest TAP Plugin ----------------- .. note:: To use this plugin, install it with ``pip install pytest-tap``. The **pytest** TAP plugin is configured from command line flags. Since **pytest** automatically activates the TAP plugin, the plugin does nothing by default. Users must enable a TAP output mode (via ``--tap-stream|files|combined``) or the plugin will take no action. * ``--tap-stream`` - Enable streaming mode to send TAP output directly to the output stream. * ``--tap-files`` - Store test results in individual test files. One test file is created for each test case. * ``--tap-combined`` - Store test results in a single output file in ``testresults.tap``. * ``--tap-outdir`` - The **pytest** TAP plugin also supports an optional output directory when you don't want to store the ``.tap`` files wherever you executed ``py.test``. Use ``--tap-outdir`` followed by a directory path to store the files in a different place. The directory will be created if it does not exist. Python and TAP -------------- The TAP specification is open-ended on certain topics. This section clarifies how tappy interprets these topics. The specification indicates that a test line represents a "test point" without explicitly defining "test point." tappy assumes that each test line is **per test method**. TAP producers in other languages may output test lines **per assertion**, but the unit of work in the Python ecosystem is the test method (i.e. ``unittest``, nose, and pytest all report per method by default). tappy does not permit setting the plan. Instead, the plan is a count of the number of test methods executed. Python test runners execute all test methods in a suite, regardless of any errors encountered. Thus, the test method count should be an accurate measure for the plan. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640755995.0 tap.py-3.1/docs/releases.rst0000644000076500000240000001377300000000000014721 0ustar00mattstaffReleases ======== Version 3.1, Released December 29, 2021 --------------------------------------- * Add support for Python 3.10. * Add support for Python 3.9. * Add support for Python 3.8. * Drop support for Python 3.5 (it is end-of-life). * Fix parsing of multi-line strings in YAML blocks (#111) * Remove unmaintained i18n support. Version 3.0, Released January 10, 2020 -------------------------------------- * Drop support for Python 2 (it is end-of-life). * Add support for subtests. * Run a test suite with ``python -m tap``. * Discontinue use of Pipenv for managing development. Version 2.6.2, Released October 20, 2019 ---------------------------------------- * Fix bug in streaming mode that would generate tap files when the plan was already set (affected pytest). Version 2.6.1, Released September 17, 2019 ------------------------------------------ * Fix TAP version 13 support from more-itertools behavior change. Version 2.6, Released September 16, 2019 ---------------------------------------- * Add support for Python 3.7. * Drop support for Python 3.4 (it is end-of-life). Version 2.5, Released September 15, 2018 ---------------------------------------- * Add ``set_plan`` to ``Tracker`` which allows producing the ``1..N`` plan line before any tests. * Switch code style to use Black formatting. Version 2.4, Released May 29, 2018 ---------------------------------- * Add support for producing TAP version 13 output to streaming and file reports by including the ``TAP version 13`` line. Version 2.3, Released May 15, 2018 ---------------------------------- * Add optional method to install tappy for YAML support with ``pip install tap.py[yaml]``. * Make tappy version 13 compliant by adding support for parsing YAML blocks. * ``unittest.expectedFailure`` now uses a TODO directive to better align with the specification. Version 2.2, Released January 7, 2018 ------------------------------------- * Add support for Python 3.6. * Drop support for Python 3.3 (it is end-of-life). * Use Pipenv for managing development. * Switch to pytest as the development test runner. Version 2.1, Released September 23, 2016 ---------------------------------------- * Add ``Parser.parse_text`` to parse TAP provided as a string. Version 2.0, Released July 31, 2016 ----------------------------------- * Remove nose plugin. The plugin moved to the ``nose-tap`` distribution. * Remove pytest plugin. The plugin moved to the ``pytest-tap`` distribution. * Remove Pygments syntax highlighting plugin. The plugin was merged upstream directly into the Pygments project and is available without tappy. * Drop support for Python 2.6. Version 1.9, Released March 28, 2016 ------------------------------------ * ``TAPTestRunner`` has a ``set_header`` method to enable or disable test case header ouput in the TAP stream. * Add support for Python 3.5. * Perform continuous integration testing on OS X. * Drop support for Python 3.2. Version 1.8, Released November 30, 2015 --------------------------------------- * The ``tappy`` TAP consumer can read a TAP stream directly from STDIN. * Tracebacks are included as diagnostic output for failures and errors. * The ``tappy`` TAP consumer has an alternative, shorter name of ``tap``. * The pytest plugin now defaults to no output unless provided a flag. Users dependent on the old default behavior can use ``--tap-files`` to achieve the same results. * Translated into Arabic. * Translated into Chinese. * Translated into Japanese. * Translated into Russian. * Perform continuous integration testing on Windows with AppVeyor. * Improve unit test coverage to 100%. Version 1.7, Released August 19, 2015 ------------------------------------- * Provide a plugin to integrate with pytest. * Document some viable alternatives to tappy. * Translated into German. * Translated into Portuguese. Version 1.6, Released June 18, 2015 ----------------------------------- * ``TAPTestRunner`` has a ``set_stream`` method to stream all TAP output directly to an output stream instead of a file. results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-stream`` flag to stream all TAP output directly to an output stream instead of a file. * tappy is now internationalized. It is translated into Dutch, French, Italian, and Spanish. * tappy is available as a Python wheel package, the new Python packaging standard. Version 1.5, Released May 18, 2015 ---------------------------------- * ``TAPTestRunner`` has a ``set_combined`` method to collect all results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-combined`` flag to collect all results in a single output file. * ``TAPTestRunner`` has a ``set_format`` method to specify line format. * The ``nosetests`` plugin has an optional ``--tap-format`` flag to specify line format. Version 1.4, Released April 4, 2015 ----------------------------------- * Update ``setup.py`` to support Debian packaging. Include man page. Version 1.3, Released January 9, 2015 ------------------------------------- * The ``tappy`` command line tool is available as a TAP consumer. * The ``Parser`` and ``Loader`` are available as APIs for programmatic handling of TAP files and data. Version 1.2, Released December 21, 2014 --------------------------------------- * Provide a syntax highlighter for Pygments so any project using Pygments (e.g., Sphinx) can highlight TAP output. Version 1.1, Released October 23, 2014 -------------------------------------- * ``TAPTestRunner`` has a ``set_outdir`` method to specify where to store ``.tap`` files. * The ``nosetests`` plugin has an optional ``--tap-outdir`` flag to specify where to store ``.tap`` files. * tappy has backported support for Python 2.6. * tappy has support for Python 3.2, 3.3, and 3.4. * tappy has support for PyPy. Version 1.0, Released March 16, 2014 ------------------------------------ * Initial release of tappy * ``TAPTestRunner`` - A test runner for ``unittest`` modules that generates TAP files. * Provides a plugin for integrating with **nose**. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1429298097.0 tap.py-3.1/docs/sample_tap.txt0000644000076500000240000000074200000000000015242 0ustar00mattstaffTAP version 13 1..7 # This is a full sample TAP file. It should try all the functionality of TAP. ok 1 A passing test not ok A failing test ok 3 An unexpected success # TODO That was unexpected. not ok 4 An expected failure # TODO Because it is not done yet. ok 5 A skipped test # SKIP Because. Just because. not ok 6 A skipped test # SKIP Failing or not does not matter. Bail out! Something blew up. ok 7 This should not have happened because the test supposedly bailed out. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1578668932.0 tap.py-3.1/docs/tappy.1.rst0000644000076500000240000000255100000000000014402 0ustar00mattstaff:orphan: tappy manual page ================= Synopsis -------- **tappy** [*options*] <*pathname*> [<*pathname*> ...] Description ----------- The :program:`tappy` command consumes the list of tap files given as *pathname* s and produces an output similar to what the regular text test-runner from python's :py:mod:`unittest` module would. If *pathname* points to a directory, :program:`tappy` will look in that directory for ``*.tap`` files to consume. If you have a tool that consumes the `unittest` regular output, but wish to use the TAP protocol to better integrate with other tools, you may use tappy to *replay* tests from .tap files, without having to actually run the tests again (which is much faster). It is also an example of how to use the tap consumer API provided by the :py:mod:`tap` module. .. warning:: :program:`tappy`'s output will differ from the standard :py:mod:`unittest` output. Indeed it cannot reproduce error and failure messages (e.g. stack traces, ...) that are not recorded in tap files. Options ------- -h, --help show a short description and option list and exit. -v, --verbose produce verbose output Author ------ The :program:`tappy` and the :py:mod:`tap` modules were written by Matt LAYMAN (https://github.com/python-tap/tappy). This manual page was written Nicolas CANIART, for the Debian project. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.7054803 tap.py-3.1/setup.cfg0000644000076500000240000000045200000000000013243 0ustar00mattstaff[bumpversion] current_version = 3.1 commit = True tag = True parse = (?P\d+)\.(?P\d+) serialize = {major}.{minor} [bumpversion:file:setup.py] [bumpversion:file:tap/__init__.py] [flake8] max-line-length = 88 [metadata] license_file = LICENSE [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756588.0 tap.py-3.1/setup.py0000644000076500000240000000371200000000000013136 0ustar00mattstaff""" tappy is a set of tools for working with the `Test Anything Protocol (TAP) `_, a line based test protocol for recording test data in a standard way. Follow tappy development on `GitHub `_. Developer documentation is on `Read the Docs `_. """ from setuptools import find_packages, setup # The docs import setup.py for the version so only call setup when not behaving # as a module. if __name__ == "__main__": with open("docs/releases.rst", "r") as f: releases = f.read() long_description = __doc__ + "\n\n" + releases setup( name="tap.py", version="3.1", url="https://github.com/python-tap/tappy", license="BSD", author="Matt Layman", author_email="matthewlayman@gmail.com", description="Test Anything Protocol (TAP) tools", long_description=long_description, packages=find_packages(), entry_points={ "console_scripts": ["tappy = tap.main:main", "tap = tap.main:main"] }, include_package_data=True, zip_safe=False, platforms="any", install_requires=[], extras_require={"yaml": ["more-itertools", "PyYAML>=5.1"]}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", ], keywords=["TAP", "unittest"], ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1640756615.696428 tap.py-3.1/tap/0000755000076500000240000000000000000000000012205 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756588.0 tap.py-3.1/tap/__init__.py0000644000076500000240000000012300000000000014312 0ustar00mattstafffrom .runner import TAPTestRunner __all__ = ["TAPTestRunner"] __version__ = "3.1" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1576375629.0 tap.py-3.1/tap/__main__.py0000644000076500000240000000006000000000000014273 0ustar00mattstafffrom tap.main import main_module main_module() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752827.0 tap.py-3.1/tap/adapter.py0000644000076500000240000000300600000000000014176 0ustar00mattstaffclass Adapter(object): """The adapter processes a TAP test line and updates a unittest result. It is an alternative to TestCase to collect TAP results. """ failureException = AssertionError def __init__(self, filename, line): self._filename = filename self._line = line def shortDescription(self): """Get the short description for verbeose results.""" return self._line.description def __call__(self, result): """Update test result with the lines in the TAP file. Provide the interface that TestCase provides to a suite or runner. """ result.startTest(self) if self._line.skip: result.addSkip(None, self._line.directive.reason) return if self._line.todo: if self._line.ok: result.addUnexpectedSuccess(self) else: result.addExpectedFailure(self, (Exception, Exception(), None)) return if self._line.ok: result.addSuccess(self) else: self.addFailure(result) def addFailure(self, result): """Add a failure to the result.""" result.addFailure(self, (Exception, Exception(), None)) # Since TAP will not provide assertion data, clean up the assertion # section so it is not so spaced out. test, err = result.failures[-1] result.failures[-1] = (test, "") def __repr__(self): return "".format(filename=self._filename) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752812.0 tap.py-3.1/tap/directive.py0000644000076500000240000000340700000000000014541 0ustar00mattstaffimport re class Directive(object): """A representation of a result line directive.""" skip_pattern = re.compile( r"""^SKIP\S* (?P\s*) # Optional whitespace. (?P.*) # Slurp up the rest.""", re.IGNORECASE | re.VERBOSE, ) todo_pattern = re.compile( r"""^TODO\b # The directive name (?P\s*) # Immediately following must be whitespace. (?P.*) # Slurp up the rest.""", re.IGNORECASE | re.VERBOSE, ) def __init__(self, text): r"""Initialize the directive by parsing the text. The text is assumed to be everything after a '#\s*' on a result line. """ self._text = text self._skip = False self._todo = False self._reason = None match = self.skip_pattern.match(text) if match: self._skip = True self._reason = match.group("reason") match = self.todo_pattern.match(text) if match: if match.group("whitespace"): self._todo = True else: # Catch the case where the directive has no descriptive text. if match.group("reason") == "": self._todo = True self._reason = match.group("reason") @property def text(self): """Get the entire text.""" return self._text @property def skip(self): """Check if the directive is a SKIP type.""" return self._skip @property def todo(self): """Check if the directive is a TODO type.""" return self._todo @property def reason(self): """Get the reason for the directive.""" return self._reason ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1548268126.0 tap.py-3.1/tap/formatter.py0000644000076500000240000000137200000000000014565 0ustar00mattstaffimport traceback def format_exception(exception): """Format an exception as diagnostics output. exception is the tuple as expected from sys.exc_info. """ exception_lines = traceback.format_exception(*exception) # The lines returned from format_exception do not strictly contain # one line per element in the list (i.e. some elements have new # line characters in the middle). Normalize that oddity. lines = "".join(exception_lines).splitlines(True) return format_as_diagnostics(lines) def format_as_diagnostics(lines): """Format the lines as diagnostics output by prepending the diagnostic #. This function makes no assumptions about the line endings. """ return "".join(["# " + line for line in lines]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752821.0 tap.py-3.1/tap/line.py0000644000076500000240000001117500000000000013513 0ustar00mattstafftry: import yaml LOAD_YAML = True except ImportError: # pragma: no cover LOAD_YAML = False class Line(object): """Base type for TAP data. TAP is a line based protocol. Thus, the most primitive type is a line. """ @property def category(self): raise NotImplementedError class Result(Line): """Information about an individual test line.""" def __init__( self, ok, number=None, description="", directive=None, diagnostics=None, raw_yaml_block=None, ): self._ok = ok if number: self._number = int(number) else: # The number may be an empty string so explicitly set to None. self._number = None self._description = description self.directive = directive self.diagnostics = diagnostics self._yaml_block = raw_yaml_block @property def category(self): """:returns: ``test``""" return "test" @property def ok(self): """Get the ok status. :rtype: bool """ return self._ok @property def number(self): """Get the test number. :rtype: int """ return self._number @property def description(self): """Get the description.""" return self._description @property def skip(self): """Check if this test was skipped. :rtype: bool """ return self.directive.skip @property def todo(self): """Check if this test was a TODO. :rtype: bool """ return self.directive.todo @property def yaml_block(self): """Lazy load a yaml_block. If yaml support is not available, there is an error in parsing the yaml block, or no yaml is associated with this result, ``None`` will be returned. :rtype: dict """ if LOAD_YAML and self._yaml_block is not None: try: yaml_dict = yaml.load(self._yaml_block, Loader=yaml.SafeLoader) return yaml_dict except yaml.error.YAMLError: print("Error parsing yaml block. Check formatting.") return None def __str__(self): is_not = "" if not self.ok: is_not = "not " directive = "" if self.directive is not None and self.directive.text: directive = " # {0}".format(self.directive.text) diagnostics = "" if self.diagnostics is not None: diagnostics = "\n" + self.diagnostics.rstrip() return "{0}ok {1} {2}{3}{4}".format( is_not, self.number, self.description, directive, diagnostics ) class Plan(Line): """A plan line to indicate how many tests to expect.""" def __init__(self, expected_tests, directive=None): self._expected_tests = expected_tests self.directive = directive @property def category(self): """:returns: ``plan``""" return "plan" @property def expected_tests(self): """Get the number of expected tests. :rtype: int """ return self._expected_tests @property def skip(self): """Check if this plan should skip the file. :rtype: bool """ return self.directive.skip class Diagnostic(Line): """A diagnostic line (i.e. anything starting with a hash).""" def __init__(self, text): self._text = text @property def category(self): """:returns: ``diagnostic``""" return "diagnostic" @property def text(self): """Get the text.""" return self._text class Bail(Line): """A bail out line (i.e. anything starting with 'Bail out!').""" def __init__(self, reason): self._reason = reason @property def category(self): """:returns: ``bail``""" return "bail" @property def reason(self): """Get the reason.""" return self._reason class Version(Line): """A version line (i.e. of the form 'TAP version 13').""" def __init__(self, version): self._version = version @property def category(self): """:returns: ``version``""" return "version" @property def version(self): """Get the version number. :rtype: int """ return self._version class Unknown(Line): """A line that represents something that is not a known TAP line. This exists for the purpose of a Null Object pattern. """ @property def category(self): """:returns: ``unknown``""" return "unknown" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752810.0 tap.py-3.1/tap/loader.py0000644000076500000240000000560600000000000014034 0ustar00mattstaffimport os import unittest from tap.adapter import Adapter from tap.parser import Parser from tap.rules import Rules class Loader(object): """Load TAP lines into unittest-able objects.""" ignored_lines = set(["diagnostic", "unknown"]) def __init__(self): self._parser = Parser() def load(self, files): """Load any files found into a suite. Any directories are walked and their files are added as TAP files. :returns: A ``unittest.TestSuite`` instance """ suite = unittest.TestSuite() for filepath in files: if os.path.isdir(filepath): self._find_tests_in_directory(filepath, suite) else: suite.addTest(self.load_suite_from_file(filepath)) return suite def load_suite_from_file(self, filename): """Load a test suite with test lines from the provided TAP file. :returns: A ``unittest.TestSuite`` instance """ suite = unittest.TestSuite() rules = Rules(filename, suite) if not os.path.exists(filename): rules.handle_file_does_not_exist() return suite line_generator = self._parser.parse_file(filename) return self._load_lines(filename, line_generator, suite, rules) def load_suite_from_stdin(self): """Load a test suite with test lines from the TAP stream on STDIN. :returns: A ``unittest.TestSuite`` instance """ suite = unittest.TestSuite() rules = Rules("stream", suite) line_generator = self._parser.parse_stdin() return self._load_lines("stream", line_generator, suite, rules) def _find_tests_in_directory(self, directory, suite): """Find test files in the directory and add them to the suite.""" for dirpath, dirnames, filenames in os.walk(directory): for filename in filenames: filepath = os.path.join(dirpath, filename) suite.addTest(self.load_suite_from_file(filepath)) def _load_lines(self, filename, line_generator, suite, rules): """Load a suite with lines produced by the line generator.""" line_counter = 0 for line in line_generator: line_counter += 1 if line.category in self.ignored_lines: continue if line.category == "test": suite.addTest(Adapter(filename, line)) rules.saw_test() elif line.category == "plan": if line.skip: rules.handle_skipping_plan(line) return suite rules.saw_plan(line, line_counter) elif line.category == "bail": rules.handle_bail(line) return suite elif line.category == "version": rules.saw_version_at(line_counter) rules.check(line_counter) return suite ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752805.0 tap.py-3.1/tap/main.py0000644000076500000240000000411400000000000013503 0ustar00mattstaffimport argparse import sys import unittest from tap.loader import Loader from tap.runner import TAPTestRunner def main(argv=sys.argv, stream=sys.stderr): """Entry point for ``tappy`` command.""" args = parse_args(argv) suite = build_suite(args) runner = unittest.TextTestRunner(verbosity=args.verbose, stream=stream) result = runner.run(suite) return get_status(result) def build_suite(args): """Build a test suite by loading TAP files or a TAP stream.""" loader = Loader() if len(args.files) == 0 or args.files[0] == "-": suite = loader.load_suite_from_stdin() else: suite = loader.load(args.files) return suite def parse_args(argv): description = "A TAP consumer for Python" epilog = ( "When no files are given or a dash (-) is used for the file name, " "tappy will read a TAP stream from STDIN." ) parser = argparse.ArgumentParser(description=description, epilog=epilog) parser.add_argument( "files", metavar="FILE", nargs="*", help=( "A file containing TAP output. Any directories listed will be " "scanned for files to include as TAP files." ), ) parser.add_argument( "-v", "--verbose", action="store_const", default=1, const=2, help="use verbose messages", ) # argparse expects the executable to be removed from argv. args = parser.parse_args(argv[1:]) # When no files are provided, the user wants to use a TAP stream on STDIN. # But they probably didn't mean it if there is no pipe connected. # In that case, print the help and exit. if not args.files and sys.stdin.isatty(): sys.exit(parser.print_help()) return args def get_status(result): """Get a return status from the result.""" if result.wasSuccessful(): return 0 else: return 1 def main_module(): """Entry point for running as ``python -m tap``.""" runner = TAPTestRunner() runner.set_stream(True) unittest.main(module=None, testRunner=runner) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752817.0 tap.py-3.1/tap/parser.py0000644000076500000240000001531500000000000014060 0ustar00mattstafffrom io import StringIO import itertools import re import sys from tap.directive import Directive from tap.line import Bail, Diagnostic, Plan, Result, Unknown, Version try: from more_itertools import peekable import yaml # noqa ENABLE_VERSION_13 = True except ImportError: # pragma: no cover ENABLE_VERSION_13 = False class Parser(object): """A parser for TAP files and lines.""" # ok and not ok share most of the same characteristics. result_base = r""" \s* # Optional whitespace. (?P\d*) # Optional test number. \s* # Optional whitespace. (?P[^#]*) # Optional description before #. \#? # Optional directive marker. \s* # Optional whitespace. (?P.*) # Optional directive text. """ ok = re.compile(r"^ok" + result_base, re.VERBOSE) not_ok = re.compile(r"^not\ ok" + result_base, re.VERBOSE) plan = re.compile( r""" ^1..(?P\d+) # Match the plan details. [^#]* # Consume any non-hash character to confirm only # directives appear with the plan details. \#? # Optional directive marker. \s* # Optional whitespace. (?P.*) # Optional directive text. """, re.VERBOSE, ) diagnostic = re.compile(r"^#") bail = re.compile( r""" ^Bail\ out! \s* # Optional whitespace. (?P.*) # Optional reason. """, re.VERBOSE, ) version = re.compile(r"^TAP version (?P\d+)$") yaml_block_start = re.compile(r"^(?P\s+)-") yaml_block_end = re.compile(r"^\s+\.\.\.") TAP_MINIMUM_DECLARED_VERSION = 13 def parse_file(self, filename): """Parse a TAP file to an iterable of tap.line.Line objects. This is a generator method that will yield an object for each parsed line. The file given by `filename` is assumed to exist. """ return self.parse(open(filename, "r")) def parse_stdin(self): """Parse a TAP stream from standard input. Note: this has the side effect of closing the standard input filehandle after parsing. """ return self.parse(sys.stdin) def parse_text(self, text): """Parse a string containing one or more lines of TAP output.""" return self.parse(StringIO(text)) def parse(self, fh): """Generate tap.line.Line objects, given a file-like object `fh`. `fh` may be any object that implements both the iterator and context management protocol (i.e. it can be used in both a "with" statement and a "for...in" statement.) Trailing whitespace and newline characters will be automatically stripped from the input lines. """ with fh: try: first_line = next(fh) except StopIteration: return first_parsed = self.parse_line(first_line.rstrip()) fh_new = itertools.chain([first_line], fh) if first_parsed.category == "version" and first_parsed.version >= 13: if ENABLE_VERSION_13: fh_new = peekable(itertools.chain([first_line], fh)) else: # pragma no cover print( """ WARNING: Optional imports not found, TAP 13 output will be ignored. To parse yaml, see requirements in docs: https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""" ) for line in fh_new: yield self.parse_line(line.rstrip(), fh_new) def parse_line(self, text, fh=None): """Parse a line into whatever TAP category it belongs.""" match = self.ok.match(text) if match: return self._parse_result(True, match, fh) match = self.not_ok.match(text) if match: return self._parse_result(False, match, fh) if self.diagnostic.match(text): return Diagnostic(text) match = self.plan.match(text) if match: return self._parse_plan(match) match = self.bail.match(text) if match: return Bail(match.group("reason")) match = self.version.match(text) if match: return self._parse_version(match) return Unknown() def _parse_plan(self, match): """Parse a matching plan line.""" expected_tests = int(match.group("expected")) directive = Directive(match.group("directive")) # Only SKIP directives are allowed in the plan. if directive.text and not directive.skip: return Unknown() return Plan(expected_tests, directive) def _parse_result(self, ok, match, fh=None): """Parse a matching result line into a result instance.""" peek_match = None try: if fh is not None and ENABLE_VERSION_13 and isinstance(fh, peekable): peek_match = self.yaml_block_start.match(fh.peek()) except StopIteration: pass if peek_match is None: return Result( ok, number=match.group("number"), description=match.group("description").strip(), directive=Directive(match.group("directive")), ) indent = peek_match.group("indent") concat_yaml = self._extract_yaml_block(indent, fh) return Result( ok, number=match.group("number"), description=match.group("description").strip(), directive=Directive(match.group("directive")), raw_yaml_block=concat_yaml, ) def _extract_yaml_block(self, indent, fh): """Extract a raw yaml block from a file handler""" raw_yaml = [] indent_match = re.compile(r"^{}".format(indent)) try: next(fh) while indent_match.match(fh.peek()): yaml_line = next(fh).replace(indent, "", 1) raw_yaml.append(yaml_line.rstrip("\n")) # check for the end and stop adding yaml if encountered if self.yaml_block_end.match(fh.peek()): next(fh) break except StopIteration: pass return "\n".join(raw_yaml) def _parse_version(self, match): version = int(match.group("version")) if version < self.TAP_MINIMUM_DECLARED_VERSION: raise ValueError( "It is an error to explicitly specify any version lower than 13." ) return Version(version) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752752.0 tap.py-3.1/tap/rules.py0000644000076500000240000000661500000000000013721 0ustar00mattstafffrom tap.adapter import Adapter from tap.directive import Directive from tap.line import Result class Rules(object): def __init__(self, filename, suite): self._filename = filename self._suite = suite self._lines_seen = {"plan": [], "test": 0, "version": []} def check(self, final_line_count): """Check the status of all provided data and update the suite.""" if self._lines_seen["version"]: self._process_version_lines() self._process_plan_lines(final_line_count) def _process_version_lines(self): """Process version line rules.""" if len(self._lines_seen["version"]) > 1: self._add_error("Multiple version lines appeared.") elif self._lines_seen["version"][0] != 1: self._add_error("The version must be on the first line.") def _process_plan_lines(self, final_line_count): """Process plan line rules.""" if not self._lines_seen["plan"]: self._add_error("Missing a plan.") return if len(self._lines_seen["plan"]) > 1: self._add_error("Only one plan line is permitted per file.") return plan, at_line = self._lines_seen["plan"][0] if not self._plan_on_valid_line(at_line, final_line_count): self._add_error("A plan must appear at the beginning or end of the file.") return if plan.expected_tests != self._lines_seen["test"]: self._add_error( "Expected {expected_count} tests but only {seen_count} ran.".format( expected_count=plan.expected_tests, seen_count=self._lines_seen["test"], ) ) def _plan_on_valid_line(self, at_line, final_line_count): """Check if a plan is on a valid line.""" # Put the common cases first. if at_line == 1 or at_line == final_line_count: return True # The plan may only appear on line 2 if the version is at line 1. after_version = ( self._lines_seen["version"] and self._lines_seen["version"][0] == 1 and at_line == 2 ) if after_version: return True return False def handle_bail(self, bail): """Handle a bail line.""" self._add_error("Bailed: {reason}".format(reason=bail.reason)) def handle_file_does_not_exist(self): """Handle a test file that does not exist.""" self._add_error("{filename} does not exist.".format(filename=self._filename)) def handle_skipping_plan(self, skip_plan): """Handle a plan that contains a SKIP directive.""" skip_line = Result(True, None, skip_plan.directive.text, Directive("SKIP")) self._suite.addTest(Adapter(self._filename, skip_line)) def saw_plan(self, plan, at_line): """Record when a plan line was seen.""" self._lines_seen["plan"].append((plan, at_line)) def saw_test(self): """Record when a test line was seen.""" self._lines_seen["test"] += 1 def saw_version_at(self, line_counter): """Record when a version line was seen.""" self._lines_seen["version"].append(line_counter) def _add_error(self, message): """Add an error test to the suite.""" error_line = Result(False, None, message, Directive("")) self._suite.addTest(Adapter(self._filename, error_line)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752824.0 tap.py-3.1/tap/runner.py0000644000076500000240000001212700000000000014073 0ustar00mattstaffimport os from unittest import TextTestResult, TextTestRunner from unittest.runner import _WritelnDecorator import sys from tap import formatter from tap.tracker import Tracker class TAPTestResult(TextTestResult): FORMAT = None def __init__(self, stream, descriptions, verbosity): super(TAPTestResult, self).__init__(stream, descriptions, verbosity) def addSubTest(self, test, subtest, err): super(TAPTestResult, self).addSubTest(test, subtest, err) if err is not None: diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( self._cls_name(test), self._description(subtest), diagnostics=diagnostics, ) else: self.tracker.add_ok(self._cls_name(test), self._description(subtest)) def stopTestRun(self): """Once the test run is complete, generate each of the TAP files.""" super(TAPTestResult, self).stopTestRun() self.tracker.generate_tap_reports() def addError(self, test, err): super(TAPTestResult, self).addError(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( self._cls_name(test), self._description(test), diagnostics=diagnostics ) def addFailure(self, test, err): super(TAPTestResult, self).addFailure(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( self._cls_name(test), self._description(test), diagnostics=diagnostics ) def addSuccess(self, test): super(TAPTestResult, self).addSuccess(test) self.tracker.add_ok(self._cls_name(test), self._description(test)) def addSkip(self, test, reason): super(TAPTestResult, self).addSkip(test, reason) self.tracker.add_skip(self._cls_name(test), self._description(test), reason) def addExpectedFailure(self, test, err): super(TAPTestResult, self).addExpectedFailure(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( self._cls_name(test), self._description(test), "TODO {}".format("(expected failure)"), diagnostics=diagnostics, ) def addUnexpectedSuccess(self, test): super(TAPTestResult, self).addUnexpectedSuccess(test) self.tracker.add_ok( self._cls_name(test), self._description(test), "TODO {}".format("(unexpected success)"), ) def _cls_name(self, test): return test.__class__.__name__ def _description(self, test): if self.FORMAT: try: return self.FORMAT.format( method_name=str(test), short_description=test.shortDescription() or "", ) except KeyError: sys.exit( "Bad format string: {format}\n" "Replacement options are: {{short_description}} and " "{{method_name}}".format(format=self.FORMAT) ) return test.shortDescription() or str(test) # TODO: 2016-7-30 mblayman - Since the 2.6 signature is no longer relevant, # check the possibility of removing the module level scope. # Module level state stinks, but this is the only way to keep compatibility # with Python 2.6. The best place for the tracker is as an instance variable # on the runner, but __init__ is so different that it is not easy to create # a runner that satisfies every supported Python version. _tracker = Tracker() class TAPTestRunner(TextTestRunner): """A test runner that will behave exactly like TextTestRunner and will additionally generate TAP files for each test case""" resultclass = TAPTestResult def set_stream(self, streaming): """Set the streaming boolean option to stream TAP directly to stdout. The test runner default output will be suppressed in favor of TAP. """ self.stream = _WritelnDecorator(open(os.devnull, "w")) _tracker.streaming = streaming _tracker.stream = sys.stdout def _makeResult(self): result = self.resultclass(self.stream, self.descriptions, self.verbosity) result.tracker = _tracker return result @classmethod def set_outdir(cls, outdir): """Set the output directory so that TAP files are written to the specified outdir location. """ # Blame the lack of unittest extensibility for this hacky method. _tracker.outdir = outdir @classmethod def set_combined(cls, combined): """Set the tracker to use a single output file.""" _tracker.combined = combined @classmethod def set_header(cls, header): """Set the header display flag.""" _tracker.header = header @classmethod def set_format(cls, fmt): """Set the format of each test line. The format string can use: * {method_name}: The test method name * {short_description}: The test's docstring short description """ TAPTestResult.FORMAT = fmt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.7037694 tap.py-3.1/tap/tests/0000755000076500000240000000000000000000000013347 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752845.0 tap.py-3.1/tap/tests/__init__.py0000644000076500000240000000010700000000000015456 0ustar00mattstaff"""Tests for tappy""" from tap.tests.testcase import TestCase # NOQA ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752843.0 tap.py-3.1/tap/tests/factory.py0000644000076500000240000000205700000000000015374 0ustar00mattstaffimport sys import tempfile from unittest.runner import TextTestResult from tap.directive import Directive from tap.line import Bail, Plan, Result class Factory(object): """A factory to produce commonly needed objects""" def make_ok(self, directive_text=""): return Result(True, 1, "This is a description.", Directive(directive_text)) def make_not_ok(self, directive_text=""): return Result(False, 1, "This is a description.", Directive(directive_text)) def make_bail(self, reason="Because it is busted."): return Bail(reason) def make_plan(self, expected_tests=99, directive_text=""): return Plan(expected_tests, Directive(directive_text)) def make_test_result(self): stream = tempfile.TemporaryFile(mode="w") return TextTestResult(stream, None, 1) def make_exc(self): """Make a traceback tuple. Doing this intentionally is not straight forward. """ try: raise ValueError("boom") except ValueError: return sys.exc_info() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752854.0 tap.py-3.1/tap/tests/run.py0000644000076500000240000000071000000000000014523 0ustar00mattstaffimport os import sys import unittest from tap import TAPTestRunner if __name__ == "__main__": tests_dir = os.path.dirname(os.path.abspath(__file__)) loader = unittest.TestLoader() tests = loader.discover(tests_dir) runner = TAPTestRunner() runner.set_outdir("testout") runner.set_format("Hi: {method_name} - {short_description}") result = runner.run(tests) status = 0 if result.wasSuccessful() else 1 sys.exit(status) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752838.0 tap.py-3.1/tap/tests/test_adapter.py0000644000076500000240000000353000000000000016401 0ustar00mattstafffrom unittest import mock from tap.adapter import Adapter from tap.tests import TestCase class TestAdapter(TestCase): """Tests for tap.adapter.Adapter""" def test_adapter_has_filename(self): """The adapter has a TAP filename.""" tap_filename = "fake.tap" adapter = Adapter(tap_filename, None) self.assertEqual(tap_filename, adapter._filename) def test_handles_ok_test_line(self): """Add a success for an ok test line.""" ok_line = self.factory.make_ok() adapter = Adapter("fake.tap", ok_line) result = mock.Mock() adapter(result) self.assertTrue(result.addSuccess.called) def test_handles_skip_test_line(self): """Add a skip when a test line contains a skip directive.""" skip_line = self.factory.make_ok(directive_text="SKIP This is the reason.") adapter = Adapter("fake.tap", skip_line) result = self.factory.make_test_result() adapter(result) self.assertEqual(1, len(result.skipped)) self.assertEqual("This is the reason.", result.skipped[0][1]) def test_handles_ok_todo_test_line(self): """Add an unexpected success for an ok todo test line.""" todo_line = self.factory.make_ok(directive_text="TODO An incomplete test") adapter = Adapter("fake.tap", todo_line) result = self.factory.make_test_result() adapter(result) self.assertEqual(1, len(result.unexpectedSuccesses)) def test_handles_not_ok_todo_test_line(self): """Add an expected failure for a not ok todo test line.""" todo_line = self.factory.make_not_ok(directive_text="TODO An incomplete test") adapter = Adapter("fake.tap", todo_line) result = self.factory.make_test_result() adapter(result) self.assertEqual(1, len(result.expectedFailures)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752865.0 tap.py-3.1/tap/tests/test_directive.py0000644000076500000240000000231100000000000016733 0ustar00mattstaffimport unittest from tap.directive import Directive class TestDirective(unittest.TestCase): """Tests for tap.directive.Directive""" def test_finds_todo(self): text = "ToDo This is something to do." directive = Directive(text) self.assertTrue(directive.todo) def test_finds_simplest_todo(self): text = "TODO" directive = Directive(text) self.assertTrue(directive.todo) def test_todo_has_boundary(self): """TAP spec indicates TODO directives must be on a boundary.""" text = "TODO: Not a TODO directive because of an immediate colon." directive = Directive(text) self.assertFalse(directive.todo) def test_finds_skip(self): text = "Skipping This is something to skip." directive = Directive(text) self.assertTrue(directive.skip) def test_finds_simplest_skip(self): text = "SKIP" directive = Directive(text) self.assertTrue(directive.skip) def test_skip_at_beginning(self): """Only match SKIP directives at the beginning.""" text = "This is not something to skip." directive = Directive(text) self.assertFalse(directive.skip) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1548268126.0 tap.py-3.1/tap/tests/test_formatter.py0000644000076500000240000000113100000000000016757 0ustar00mattstafffrom tap.formatter import format_as_diagnostics, format_exception from tap.tests import TestCase class TestFormatter(TestCase): def test_formats_as_diagnostics(self): data = ["foo\n", "bar\n"] expected_diagnostics = "# foo\n# bar\n" diagnostics = format_as_diagnostics(data) self.assertEqual(expected_diagnostics, diagnostics) def test_format_exception_as_diagnostics(self): exc = self.factory.make_exc() diagnostics = format_exception(exc) self.assertTrue(diagnostics.startswith("# ")) self.assertTrue("boom" in diagnostics) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752849.0 tap.py-3.1/tap/tests/test_line.py0000644000076500000240000000232100000000000015705 0ustar00mattstaffimport unittest from tap.directive import Directive from tap.line import Line, Result class TestLine(unittest.TestCase): """Tests for tap.line.Line""" def test_line_requires_category(self): line = Line() with self.assertRaises(NotImplementedError): line.category class TestResult(unittest.TestCase): """Tests for tap.line.Result""" def test_category(self): result = Result(True) self.assertEqual("test", result.category) def test_ok(self): result = Result(True) self.assertTrue(result.ok) def test_str_ok(self): result = Result(True, 42, "passing") self.assertEqual("ok 42 passing", str(result)) def test_str_not_ok(self): result = Result(False, 43, "failing") self.assertEqual("not ok 43 failing", str(result)) def test_str_directive(self): directive = Directive("SKIP a reason") result = Result(True, 44, "passing", directive) self.assertEqual("ok 44 passing # SKIP a reason", str(result)) def test_str_diagnostics(self): result = Result(False, 43, "failing", diagnostics="# more info") self.assertEqual("not ok 43 failing\n# more info", str(result)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752841.0 tap.py-3.1/tap/tests/test_loader.py0000644000076500000240000000724200000000000016233 0ustar00mattstaffimport inspect from io import StringIO import os import tempfile import unittest from unittest import mock from tap.loader import Loader from tap.tests import TestCase class TestLoader(TestCase): """Tests for tap.loader.Loader""" def test_handles_file(self): """The loader handles a file.""" sample = inspect.cleandoc( """TAP version 13 1..2 # This is a diagnostic. ok 1 A passing test not ok 2 A failing test This is an unknown line. Bail out! This test would abort. """ ) temp = tempfile.NamedTemporaryFile(delete=False) temp.write(sample.encode("utf-8")) temp.close() loader = Loader() suite = loader.load_suite_from_file(temp.name) # The bail line counts as a failed test. self.assertEqual(3, len(suite._tests)) def test_file_does_not_exist(self): """The loader records a failure when a file does not exist.""" loader = Loader() suite = loader.load_suite_from_file("phony.tap") self.assertEqual(1, len(suite._tests)) self.assertEqual( "{filename} does not exist.".format(filename="phony.tap"), suite._tests[0]._line.description, ) def test_handles_directory(self): directory = tempfile.mkdtemp() sub_directory = os.path.join(directory, "sub") os.mkdir(sub_directory) with open(os.path.join(directory, "a_file.tap"), "w") as f: f.write("ok A passing test") with open(os.path.join(sub_directory, "another_file.tap"), "w") as f: f.write("not ok A failing test") loader = Loader() suite = loader.load([directory]) self.assertEqual(2, len(suite._tests)) def test_errors_with_multiple_version_lines(self): sample = inspect.cleandoc( """TAP version 13 TAP version 13 1..0 """ ) temp = tempfile.NamedTemporaryFile(delete=False) temp.write(sample.encode("utf-8")) temp.close() loader = Loader() suite = loader.load_suite_from_file(temp.name) self.assertEqual(1, len(suite._tests)) self.assertEqual( "Multiple version lines appeared.", suite._tests[0]._line.description ) def test_errors_with_version_not_on_first_line(self): sample = inspect.cleandoc( """# Something that doesn't belong. TAP version 13 1..0 """ ) temp = tempfile.NamedTemporaryFile(delete=False) temp.write(sample.encode("utf-8")) temp.close() loader = Loader() suite = loader.load_suite_from_file(temp.name) self.assertEqual(1, len(suite._tests)) self.assertEqual( "The version must be on the first line.", suite._tests[0]._line.description, ) def test_skip_plan_aborts_loading(self): sample = inspect.cleandoc( """1..0 # Skipping this test file. ok This should not get processed. """ ) temp = tempfile.NamedTemporaryFile(delete=False) temp.write(sample.encode("utf-8")) temp.close() loader = Loader() suite = loader.load_suite_from_file(temp.name) self.assertEqual(1, len(suite._tests)) self.assertEqual("Skipping this test file.", suite._tests[0]._line.description) @mock.patch("tap.parser.sys.stdin", StringIO(u"")) def test_loads_from_stream(self): loader = Loader() suite = loader.load_suite_from_stdin() self.assertTrue(isinstance(suite, unittest.TestSuite)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752867.0 tap.py-3.1/tap/tests/test_main.py0000644000076500000240000000373400000000000015713 0ustar00mattstaffimport argparse import os from unittest import mock from tap.loader import Loader from tap.main import build_suite, get_status, main, main_module, parse_args from tap.tests import TestCase class TestMain(TestCase): """Tests for tap.main""" def test_exits_with_error(self): """The main function returns an error status if there were failures.""" argv = ["/bin/fake", "fake.tap"] stream = open(os.devnull, "w") status = main(argv, stream=stream) self.assertEqual(1, status) def test_get_successful_status(self): result = mock.Mock() result.wasSuccessful.return_value = True self.assertEqual(0, get_status(result)) @mock.patch.object(Loader, "load_suite_from_stdin") def test_build_suite_from_stdin(self, load_suite_from_stdin): args = mock.Mock() args.files = [] expected_suite = mock.Mock() load_suite_from_stdin.return_value = expected_suite suite = build_suite(args) self.assertEqual(expected_suite, suite) @mock.patch.object(Loader, "load_suite_from_stdin") def test_build_suite_from_stdin_dash(self, load_suite_from_stdin): argv = ["/bin/fake", "-"] args = parse_args(argv) expected_suite = mock.Mock() load_suite_from_stdin.return_value = expected_suite suite = build_suite(args) self.assertEqual(expected_suite, suite) @mock.patch("tap.main.sys.stdin") @mock.patch("tap.main.sys.exit") @mock.patch.object(argparse.ArgumentParser, "print_help") def test_when_no_pipe_to_stdin(self, print_help, sys_exit, mock_stdin): argv = ["/bin/fake"] mock_stdin.isatty = mock.Mock(return_value=True) parse_args(argv) self.assertTrue(print_help.called) self.assertTrue(sys_exit.called) class TestMainModule(TestCase): @mock.patch("tap.main.unittest") def test_main_set_to_stream(self, mock_unittest): main_module() mock_unittest.main.called ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752861.0 tap.py-3.1/tap/tests/test_parser.py0000644000076500000240000004036100000000000016260 0ustar00mattstafffrom contextlib import contextmanager import inspect from io import BytesIO, StringIO import sys import tempfile import unittest from unittest import mock from tap.parser import Parser try: import yaml from more_itertools import peekable # noqa have_yaml = True except ImportError: have_yaml = False @contextmanager def captured_output(): if sys.version_info[0] < 3: new_out, new_err = BytesIO(), BytesIO() else: new_out, new_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: sys.stdout, sys.stderr = old_out, old_err class TestParser(unittest.TestCase): """Tests for tap.parser.Parser""" def test_finds_ok(self): """The parser extracts an ok line.""" parser = Parser() line = parser.parse_line("ok - This is a passing test line.") self.assertEqual("test", line.category) self.assertTrue(line.ok) self.assertTrue(line.number is None) def test_finds_number(self): """The parser extracts a test number.""" parser = Parser() line = parser.parse_line("ok 42 is the magic number.") self.assertEqual("test", line.category) self.assertEqual(42, line.number) def test_finds_description(self): parser = Parser() line = parser.parse_line("ok 42 A passing test.") self.assertEqual("test", line.category) self.assertEqual("A passing test.", line.description) def test_after_hash_is_not_description(self): parser = Parser() line = parser.parse_line("ok A description # Not part of description.") self.assertEqual("test", line.category) self.assertEqual("A description", line.description) def test_finds_todo(self): parser = Parser() line = parser.parse_line("ok A description # TODO Not done") self.assertEqual("test", line.category) self.assertTrue(line.todo) def test_finds_skip(self): parser = Parser() line = parser.parse_line("ok A description # SKIP for now") self.assertEqual("test", line.category) self.assertTrue(line.skip) def test_finds_not_ok(self): """The parser extracts a not ok line.""" parser = Parser() line = parser.parse_line("not ok - This is a failing test line.") self.assertEqual("test", line.category) self.assertFalse(line.ok) self.assertTrue(line.number is None) self.assertEqual("", line.directive.text) def test_finds_directive(self): """The parser extracts a directive""" parser = Parser() test_line = "not ok - This line fails # TODO not implemented" line = parser.parse_line(test_line) directive = line.directive self.assertEqual("test", line.category) self.assertEqual("TODO not implemented", directive.text) self.assertFalse(directive.skip) self.assertTrue(directive.todo) self.assertEqual("not implemented", directive.reason) def test_unrecognizable_line(self): """The parser returns an unrecognizable line.""" parser = Parser() line = parser.parse_line("This is not a valid TAP line. # srsly") self.assertEqual("unknown", line.category) def test_diagnostic_line(self): """The parser extracts a diagnostic line.""" text = "# An example diagnostic line" parser = Parser() line = parser.parse_line(text) self.assertEqual("diagnostic", line.category) self.assertEqual(text, line.text) def test_bail_out_line(self): """The parser extracts a bail out line.""" parser = Parser() line = parser.parse_line("Bail out! This is the reason to bail.") self.assertEqual("bail", line.category) self.assertEqual("This is the reason to bail.", line.reason) def test_finds_version(self): """The parser extracts a version line.""" parser = Parser() line = parser.parse_line("TAP version 13") self.assertEqual("version", line.category) self.assertEqual(13, line.version) def test_errors_on_old_version(self): """The TAP spec dictates that anything less than 13 is an error.""" parser = Parser() with self.assertRaises(ValueError): parser.parse_line("TAP version 12") def test_finds_plan(self): """The parser extracts a plan line.""" parser = Parser() line = parser.parse_line("1..42") self.assertEqual("plan", line.category) self.assertEqual(42, line.expected_tests) def test_finds_plan_with_skip(self): """The parser extracts a plan line containing a SKIP.""" parser = Parser() line = parser.parse_line("1..42 # Skipping this test file.") self.assertEqual("plan", line.category) self.assertTrue(line.skip) def test_ignores_plan_with_any_non_skip_directive(self): """The parser only recognizes SKIP directives in plans.""" parser = Parser() line = parser.parse_line("1..42 # TODO will not work.") self.assertEqual("unknown", line.category) def test_parses_text(self): sample = inspect.cleandoc( u"""1..2 ok 1 A passing test not ok 2 A failing test""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) self.assertEqual(3, len(lines)) self.assertEqual("plan", lines[0].category) self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) def test_parses_file(self): sample = inspect.cleandoc( """1..2 ok 1 A passing test not ok 2 A failing test""" ) temp = tempfile.NamedTemporaryFile(delete=False) temp.write(sample.encode("utf-8")) temp.close() parser = Parser() lines = [] for line in parser.parse_file(temp.name): lines.append(line) self.assertEqual(3, len(lines)) self.assertEqual("plan", lines[0].category) self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) self.assertIsNone(lines[1].yaml_block) self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) def test_parses_yaml(self): sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test --- test: sample yaml ... not ok 2 A failing test""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) if have_yaml: converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) else: self.assertEqual(7, len(lines)) self.assertEqual(13, lines[0].version) for line_index in list(range(3, 6)): self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[6].category) def test_parses_mixed(self): # Test that we can parse both a version 13 and earlier version files # using the same parser. Make sure that parsing works regardless of # the order of the incoming documents. sample_version_13 = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing version 13 test --- test: sample yaml ... not ok 2 A failing version 13 test""" ) sample_pre_13 = inspect.cleandoc( """1..2 ok 1 A passing pre-13 test not ok 2 A failing pre-13 test""" ) parser = Parser() lines = [] lines.extend(parser.parse_text(sample_version_13)) lines.extend(parser.parse_text(sample_pre_13)) if have_yaml: self.assertEqual(13, lines[0].version) self.assertEqual("A passing version 13 test", lines[2].description) self.assertEqual("A failing version 13 test", lines[3].description) self.assertEqual("A passing pre-13 test", lines[5].description) self.assertEqual("A failing pre-13 test", lines[6].description) else: self.assertEqual(13, lines[0].version) self.assertEqual("A passing version 13 test", lines[2].description) self.assertEqual("A failing version 13 test", lines[6].description) self.assertEqual("A passing pre-13 test", lines[8].description) self.assertEqual("A failing pre-13 test", lines[9].description) # Test parsing documents in reverse order parser = Parser() lines = [] lines.extend(parser.parse_text(sample_pre_13)) lines.extend(parser.parse_text(sample_version_13)) if have_yaml: self.assertEqual("A passing pre-13 test", lines[1].description) self.assertEqual("A failing pre-13 test", lines[2].description) self.assertEqual(13, lines[3].version) self.assertEqual("A passing version 13 test", lines[5].description) self.assertEqual("A failing version 13 test", lines[6].description) else: self.assertEqual("A passing pre-13 test", lines[1].description) self.assertEqual("A failing pre-13 test", lines[2].description) self.assertEqual(13, lines[3].version) self.assertEqual("A passing version 13 test", lines[5].description) self.assertEqual("A failing version 13 test", lines[9].description) def test_parses_yaml_no_end(self): sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test --- test: sample yaml not ok 2 A failing test""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) if have_yaml: converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) else: self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) for line_index in list(range(3, 5)): self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[5].category) def test_parses_yaml_more_complex(self): sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test --- message: test severity: fail data: got: - foo expect: - bar output: |- a multiline string must be handled properly even with | pipes | here > and: there""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) if have_yaml: converted_yaml = yaml.safe_load( u''' message: test severity: fail data: got: - foo expect: - bar output: "a multiline string\\nmust be handled properly\\neven with | pipes\\n| here > and: there"''' # noqa ) self.assertEqual(3, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) else: self.assertEqual(16, len(lines)) self.assertEqual(13, lines[0].version) for line_index in list(range(3, 11)): self.assertEqual("unknown", lines[line_index].category) def test_parses_yaml_no_association(self): sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test # Diagnostic line --- test: sample yaml ... not ok 2 A failing test""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) self.assertEqual("diagnostic", lines[3].category) for line_index in list(range(4, 7)): self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[7].category) def test_parses_yaml_no_start(self): sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test test: sample yaml ... not ok 2 A failing test""" ) parser = Parser() lines = [] for line in parser.parse_text(sample): lines.append(line) self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) for line_index in list(range(3, 5)): self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[5].category) def test_malformed_yaml(self): self.maxDiff = None sample = inspect.cleandoc( u"""TAP version 13 1..2 ok 1 A passing test --- test: sample yaml \tfail: tabs are not allowed! ... not ok 2 A failing test""" ) yaml_err = inspect.cleandoc( u""" WARNING: Optional imports not found, TAP 13 output will be ignored. To parse yaml, see requirements in docs: https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""" ) parser = Parser() lines = [] with captured_output() as (parse_out, _): for line in parser.parse_text(sample): lines.append(line) if have_yaml: self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) with captured_output() as (out, _): self.assertIsNone(lines[2].yaml_block) self.assertEqual( "Error parsing yaml block. Check formatting.", out.getvalue().strip() ) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) else: self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) for line_index in list(range(3, 7)): self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[7].category) self.assertEqual(yaml_err, parse_out.getvalue().strip()) def test_parse_empty_file(self): temp = tempfile.NamedTemporaryFile(delete=False) temp.close() parser = Parser() lines = [] for line in parser.parse_file(temp.name): lines.append(line) self.assertEqual(0, len(lines)) @mock.patch( "tap.parser.sys.stdin", StringIO( u"""1..2 ok 1 A passing test not ok 2 A failing test""" ), ) def test_parses_stdin(self): parser = Parser() lines = [] for line in parser.parse_stdin(): lines.append(line) self.assertEqual(3, len(lines)) self.assertEqual("plan", lines[0].category) self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752834.0 tap.py-3.1/tap/tests/test_result.py0000644000076500000240000000662600000000000016310 0ustar00mattstaffimport contextlib import os import unittest import unittest.case from tap.runner import TAPTestResult from tap.tests import TestCase from tap.tracker import Tracker class FakeTestCase(unittest.TestCase): def runTest(self): pass @contextlib.contextmanager def subTest(self, *args, **kwargs): try: self._subtest = unittest.case._SubTest(self, object(), {}) yield finally: self._subtest = None def __call__(self, result): pass class TestTAPTestResult(TestCase): @classmethod def _make_one(cls): # Yep, the stream is not being closed. stream = open(os.devnull, "w") result = TAPTestResult(stream, False, 0) result.tracker = Tracker() return result def test_adds_error(self): result = self._make_one() # Python 3 does some extra testing in unittest on exceptions so fake # the cause as if it were raised. ex = Exception() ex.__cause__ = None result.addError(FakeTestCase(), (None, ex, None)) self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_failure(self): result = self._make_one() # Python 3 does some extra testing in unittest on exceptions so fake # the cause as if it were raised. ex = Exception() ex.__cause__ = None result.addFailure(FakeTestCase(), (None, ex, None)) self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_success(self): result = self._make_one() result.addSuccess(FakeTestCase()) self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_skip(self): result = self._make_one() result.addSkip(FakeTestCase(), "a reason") self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_expected_failure(self): exc = self.factory.make_exc() result = self._make_one() result.addExpectedFailure(FakeTestCase(), exc) line = result.tracker._test_cases["FakeTestCase"][0] self.assertFalse(line.ok) self.assertEqual(line.directive.text, "TODO {}".format("(expected failure)")) def test_adds_unexpected_success(self): result = self._make_one() result.addUnexpectedSuccess(FakeTestCase()) line = result.tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) self.assertEqual(line.directive.text, "TODO {}".format("(unexpected success)")) def test_adds_subtest_success(self): """Test that the runner handles subtest success results.""" result = self._make_one() test = FakeTestCase() with test.subTest(): result.addSubTest(test, test._subtest, None) line = result.tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) def test_adds_subtest_failure(self): """Test that the runner handles subtest failure results.""" result = self._make_one() # Python 3 does some extra testing in unittest on exceptions so fake # the cause as if it were raised. ex = Exception() ex.__cause__ = None test = FakeTestCase() with test.subTest(): result.addSubTest(test, test._subtest, (ex.__class__, ex, None)) line = result.tracker._test_cases["FakeTestCase"][0] self.assertFalse(line.ok) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752863.0 tap.py-3.1/tap/tests/test_rules.py0000644000076500000240000000547300000000000016123 0ustar00mattstaffimport unittest from tap.rules import Rules from tap.tests import TestCase class TestRules(TestCase): """Tests for tap.rules.Rules""" def _make_one(self): self.suite = unittest.TestSuite() return Rules("foobar.tap", self.suite) def test_handles_skipping_plan(self): skip_plan = self.factory.make_plan(directive_text="Skip on Mondays.") rules = self._make_one() rules.handle_skipping_plan(skip_plan) self.assertEqual(1, len(self.suite._tests)) self.assertEqual("Skip on Mondays.", self.suite._tests[0]._line.description) def test_tracks_plan_line(self): plan = self.factory.make_plan() rules = self._make_one() rules.saw_plan(plan, 28) self.assertEqual(rules._lines_seen["plan"][0][0], plan) self.assertEqual(rules._lines_seen["plan"][0][1], 28) def test_errors_plan_not_at_end(self): plan = self.factory.make_plan() rules = self._make_one() rules.saw_plan(plan, 41) rules.check(42) self.assertEqual( "A plan must appear at the beginning or end of the file.", self.suite._tests[0]._line.description, ) def test_requires_plan(self): rules = self._make_one() rules.check(42) self.assertEqual("Missing a plan.", self.suite._tests[0]._line.description) def test_only_one_plan(self): plan = self.factory.make_plan() rules = self._make_one() rules.saw_plan(plan, 41) rules.saw_plan(plan, 42) rules.check(42) self.assertEqual( "Only one plan line is permitted per file.", self.suite._tests[0]._line.description, ) def test_plan_line_two(self): """A plan may appear on line 2 when line 1 is a version line.""" rules = self._make_one() rules.saw_version_at(1) valid = rules._plan_on_valid_line(at_line=2, final_line_count=42) self.assertTrue(valid) def test_errors_when_expected_tests_differs_from_actual(self): plan = self.factory.make_plan(expected_tests=42) rules = self._make_one() rules.saw_plan(plan, 1) rules.saw_test() rules.check(2) self.assertEqual( "Expected {expected_count} tests but only {seen_count} ran.".format( expected_count=42, seen_count=1 ), self.suite._tests[0]._line.description, ) def test_errors_on_bail(self): bail = self.factory.make_bail(reason="Missing something important.") rules = self._make_one() rules.handle_bail(bail) self.assertEqual(1, len(self.suite._tests)) self.assertEqual( "Bailed: {reason}".format(reason="Missing something important."), self.suite._tests[0]._line.description, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752858.0 tap.py-3.1/tap/tests/test_runner.py0000644000076500000240000000650400000000000016276 0ustar00mattstaffimport os import sys import tempfile import unittest from unittest import mock from tap import TAPTestRunner from tap.runner import TAPTestResult, _tracker class TestTAPTestRunner(unittest.TestCase): def test_has_tap_test_result(self): runner = TAPTestRunner() self.assertEqual(runner.resultclass, TAPTestResult) def test_runner_uses_outdir(self): """Test that the test runner sets the outdir so that TAP files will be written to that location. Setting class attributes to get the right behavior is a dirty hack, but the unittest classes aren't very extensible. """ # Save the previous outdir in case **this** execution was using it. previous_outdir = _tracker.outdir outdir = tempfile.mkdtemp() TAPTestRunner.set_outdir(outdir) self.assertEqual(outdir, _tracker.outdir) _tracker.outdir = previous_outdir def test_runner_uses_format(self): """Test that format is set on TAPTestResult FORMAT.""" # Save the previous format in case **this** execution was using it. previous_format = TAPTestResult.FORMAT fmt = "{method_name}: {short_description}" TAPTestRunner.set_format(fmt) self.assertEqual(fmt, TAPTestResult.FORMAT) TAPTestResult.FORMAT = previous_format def test_runner_uses_combined(self): """Test that output is combined.""" # Save previous combined in case **this** execution was using it. previous_combined = _tracker.combined TAPTestRunner.set_combined(True) self.assertTrue(_tracker.combined) _tracker.combined = previous_combined @mock.patch("sys.exit") def test_bad_format_string(self, fake_exit): """A bad format string exits the runner.""" previous_format = TAPTestResult.FORMAT bad_format = "Not gonna work {sort_desc}" TAPTestRunner.set_format(bad_format) result = TAPTestResult(None, True, 1) test = mock.Mock() result._description(test) self.assertTrue(fake_exit.called) TAPTestResult.FORMAT = previous_format def test_runner_sets_tracker_for_streaming(self): """The tracker is set for streaming mode.""" previous_streaming = _tracker.streaming previous_stream = _tracker.stream runner = TAPTestRunner() runner.set_stream(True) self.assertTrue(_tracker.streaming) self.assertTrue(_tracker.stream, sys.stdout) _tracker.streaming = previous_streaming _tracker.stream = previous_stream def test_runner_stream_to_devnull_for_streaming(self): previous_streaming = _tracker.streaming previous_stream = _tracker.stream runner = TAPTestRunner() runner.set_stream(True) self.assertTrue(runner.stream.stream.name, os.devnull) _tracker.streaming = previous_streaming _tracker.stream = previous_stream def test_runner_uses_header(self): """Test that the case header can be turned off.""" # Save previous header in case **this** execution was using it. previous_header = _tracker.header TAPTestRunner.set_header(False) self.assertFalse(_tracker.header) TAPTestRunner.set_header(True) self.assertTrue(_tracker.header) _tracker.header = previous_header ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752856.0 tap.py-3.1/tap/tests/test_tracker.py0000644000076500000240000002564400000000000016426 0ustar00mattstaffimport inspect from io import StringIO import os import tempfile from unittest import mock from tap.tests import TestCase from tap.tracker import Tracker class TestTracker(TestCase): def _make_header(self, test_case): return "# TAP results for {test_case}".format(test_case=test_case) def test_has_test_cases(self): tracker = Tracker() self.assertEqual(tracker._test_cases, {}) def test_tracks_class(self): tracker = Tracker() tracker._track("FakeTestClass") self.assertEqual(tracker._test_cases.get("FakeTestClass"), []) def test_adds_ok(self): tracker = Tracker() tracker.add_ok("FakeTestCase", "a description") line = tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) self.assertEqual(line.description, "a description") def test_adds_not_ok(self): tracker = Tracker() tracker.add_not_ok("FakeTestCase", "a description") line = tracker._test_cases["FakeTestCase"][0] self.assertFalse(line.ok) self.assertEqual(line.description, "a description") def test_adds_skip(self): tracker = Tracker() tracker.add_skip("FakeTestCase", "a description", "a reason") line = tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) self.assertEqual(line.description, "a description") self.assertEqual(line.directive.text, "SKIP a reason") def test_generates_tap_reports_in_new_outdir(self): tempdir = tempfile.mkdtemp() outdir = os.path.join(tempdir, "non", "existent", "path") tracker = Tracker(outdir=outdir) tracker.add_ok("FakeTestCase", "I should be in the specified dir.") tracker.generate_tap_reports() tap_file = os.path.join(outdir, "FakeTestCase.tap") self.assertTrue(os.path.exists(tap_file)) def test_generates_tap_reports_in_existing_outdir(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir) tracker.add_ok("FakeTestCase", "I should be in the specified dir.") tracker.generate_tap_reports() tap_file = os.path.join(outdir, "FakeTestCase.tap") self.assertTrue(os.path.exists(tap_file)) def test_results_not_combined_by_default(self): tracker = Tracker() self.assertFalse(tracker.combined) def test_individual_report_has_no_plan_when_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) tracker.add_ok("FakeTestCase", "Look ma, no plan!") out_file = StringIO() tracker.generate_tap_report( "FakeTestCase", tracker._test_cases["FakeTestCase"], out_file ) report = out_file.getvalue() self.assertTrue("Look ma" in report) self.assertFalse("1.." in report) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_combined_results_in_one_file_tap_version_12(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) tracker.add_ok("FakeTestCase", "YESSS!") tracker.add_ok("DifferentFakeTestCase", "GOAAL!") tracker.generate_tap_reports() self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) self.assertFalse( os.path.exists(os.path.join(outdir, "DifferentFakeTestCase.tap")) ) with open(os.path.join(outdir, "testresults.tap"), "r") as f: report = f.read() expected = inspect.cleandoc( """{header_1} ok 1 YESSS! {header_2} ok 2 GOAAL! 1..2 """.format( header_1=self._make_header("FakeTestCase"), header_2=self._make_header("DifferentFakeTestCase"), ) ) self.assertEqual(report.strip(), expected) @mock.patch("tap.tracker.ENABLE_VERSION_13", True) def test_combined_results_in_one_file_tap_version_13(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) tracker.add_ok("FakeTestCase", "YESSS!") tracker.add_ok("DifferentFakeTestCase", "GOAAL!") tracker.generate_tap_reports() self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) self.assertFalse( os.path.exists(os.path.join(outdir, "DifferentFakeTestCase.tap")) ) with open(os.path.join(outdir, "testresults.tap"), "r") as f: report = f.read() expected = inspect.cleandoc( """ TAP version 13 {header_1} ok 1 YESSS! {header_2} ok 2 GOAAL! 1..2 """.format( header_1=self._make_header("FakeTestCase"), header_2=self._make_header("DifferentFakeTestCase"), ) ) self.assertEqual(report.strip(), expected) def test_tracker_does_not_stream_by_default(self): tracker = Tracker() self.assertFalse(tracker.streaming) def test_tracker_has_stream(self): tracker = Tracker() self.assertTrue(tracker.stream is None) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.add_ok("FakeTestCase", "YESSS!") tracker.add_ok("AnotherTestCase", "Sure.") expected = inspect.cleandoc( """{header_1} ok 1 YESSS! {header_2} ok 2 Sure. """.format( header_1=self._make_header("FakeTestCase"), header_2=self._make_header("AnotherTestCase"), ) ) self.assertEqual(stream.getvalue().strip(), expected) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_not_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.add_not_ok("FakeTestCase", "YESSS!") expected = inspect.cleandoc( """{header} not ok 1 YESSS! """.format( header=self._make_header("FakeTestCase") ) ) self.assertEqual(stream.getvalue().strip(), expected) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_skip_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """{header} ok 1 YESSS! # SKIP a reason """.format( header=self._make_header("FakeTestCase") ) ) self.assertEqual(stream.getvalue().strip(), expected) def test_streaming_does_not_write_files(self): outdir = tempfile.mkdtemp() stream = StringIO() tracker = Tracker(outdir=outdir, streaming=True, stream=stream) tracker.add_ok("FakeTestCase", "YESSS!") tracker.generate_tap_reports() self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_streaming_writes_plan(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.combined_line_number = 42 tracker.generate_tap_reports() self.assertEqual(stream.getvalue(), "1..42\n") @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_streaming(self): outdir = tempfile.mkdtemp() stream = StringIO() tracker = Tracker(outdir=outdir, streaming=True, stream=stream) tracker.set_plan(123) tracker.add_ok("FakeTestCase", "YESSS!") tracker.generate_tap_reports() self.assertEqual( stream.getvalue(), "1..123\n{header}\nok 1 YESSS!\n".format( header=self._make_header("FakeTestCase") ), ) self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_immediate_streaming(self): stream = StringIO() Tracker(streaming=True, stream=stream, plan=123) self.assertEqual(stream.getvalue(), "1..123\n") @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(streaming=False, outdir=outdir, combined=True) tracker.set_plan(123) tracker.generate_tap_reports() with open(os.path.join(outdir, "testresults.tap"), "r") as f: lines = f.readlines() self.assertEqual(lines[0], "1..123\n") @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_not_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(streaming=False, outdir=outdir, combined=False) with self.assertRaises(ValueError): tracker.set_plan(123) @mock.patch("tap.tracker.ENABLE_VERSION_13", True) def test_streaming_writes_tap_version_13(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """ TAP version 13 {header} ok 1 YESSS! # SKIP a reason """.format( header=self._make_header("FakeTestCase") ) ) self.assertEqual(stream.getvalue().strip(), expected) def test_get_default_tap_file_path(self): tracker = Tracker() file_path = tracker._get_tap_file_path("foo") self.assertEqual("foo.tap", file_path) def test_sanitizes_tap_file_path(self): tracker = Tracker() file_path = tracker._get_tap_file_path("an awful \\ testcase / name\n") self.assertEqual("an-awful---testcase---name-.tap", file_path) def test_adds_not_ok_with_diagnostics(self): tracker = Tracker() tracker.add_not_ok("FakeTestCase", "a description", diagnostics="# more info\n") line = tracker._test_cases["FakeTestCase"][0] self.assertEqual("# more info\n", line.diagnostics) def test_header_displayed_by_default(self): tracker = Tracker() self.assertTrue(tracker.header) def test_header_set_by_init(self): tracker = Tracker(header=False) self.assertFalse(tracker.header) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_does_not_write_header(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream, header=False) tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """ ok 1 YESSS! # SKIP a reason """ ) self.assertEqual(stream.getvalue().strip(), expected) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752852.0 tap.py-3.1/tap/tests/testcase.py0000644000076500000240000000033600000000000015536 0ustar00mattstaffimport unittest from tap.tests.factory import Factory class TestCase(unittest.TestCase): def __init__(self, methodName="runTest"): super(TestCase, self).__init__(methodName) self.factory = Factory() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640752807.0 tap.py-3.1/tap/tracker.py0000644000076500000240000001667200000000000014226 0ustar00mattstaffimport os from tap.directive import Directive from tap.line import Result try: import more_itertools # noqa import yaml # noqa ENABLE_VERSION_13 = True except ImportError: # pragma: no cover ENABLE_VERSION_13 = False class Tracker(object): def __init__( self, outdir=None, combined=False, streaming=False, stream=None, header=True, plan=None, ): self.outdir = outdir # Combine all the test results into one file. self.combined = combined self.combined_line_number = 0 # Test case ordering is important for the combined results # because of how numbers are assigned. The test cases # must be tracked in order so that reporting can sequence # the line numbers properly. self.combined_test_cases_seen = [] # Stream output directly to a stream instead of file output. self.streaming = streaming self.stream = stream # The total number of tests we expect (or None if we don't know yet). self.plan = plan self._plan_written = False # Display the test case header unless told not to. self.header = header # Internal state for tracking each test case. self._test_cases = {} self._sanitized_table = str.maketrans(" \\/\n", "----") if self.streaming: self._write_tap_version(self.stream) if self.plan is not None: self._write_plan(self.stream) def _get_outdir(self): return self._outdir def _set_outdir(self, outdir): self._outdir = outdir if outdir and not os.path.exists(outdir): os.makedirs(outdir) outdir = property(_get_outdir, _set_outdir) def _track(self, class_name): """Keep track of which test cases have executed.""" if self._test_cases.get(class_name) is None: if self.streaming and self.header: self._write_test_case_header(class_name, self.stream) self._test_cases[class_name] = [] if self.combined: self.combined_test_cases_seen.append(class_name) def add_ok(self, class_name, description, directive=""): result = Result( ok=True, number=self._get_next_line_number(class_name), description=description, directive=Directive(directive), ) self._add_line(class_name, result) def add_not_ok(self, class_name, description, directive="", diagnostics=None): result = Result( ok=False, number=self._get_next_line_number(class_name), description=description, diagnostics=diagnostics, directive=Directive(directive), ) self._add_line(class_name, result) def add_skip(self, class_name, description, reason): directive = "SKIP {0}".format(reason) result = Result( ok=True, number=self._get_next_line_number(class_name), description=description, directive=Directive(directive), ) self._add_line(class_name, result) def _add_line(self, class_name, result): self._track(class_name) if self.streaming: print(result, file=self.stream) self._test_cases[class_name].append(result) def _get_next_line_number(self, class_name): if self.combined or self.streaming: # This has an obvious side effect. Oh well. self.combined_line_number += 1 return self.combined_line_number else: try: return len(self._test_cases[class_name]) + 1 except KeyError: # A result is created before the call to _track so the test # case may not be tracked yet. In that case, the line is 1. return 1 def set_plan(self, total): """Notify the tracker how many total tests there will be.""" self.plan = total if self.streaming: # This will only write the plan if we haven't written it # already but we want to check if we already wrote a # test out (in which case we can't just write the plan out # right here). if not self.combined_test_cases_seen: self._write_plan(self.stream) elif not self.combined: raise ValueError( "set_plan can only be used with combined or streaming output" ) def generate_tap_reports(self): """Generate TAP reports. The results are either combined into a single output file or the output file name is generated from the test case. """ if self.streaming: # We're streaming but set_plan wasn't called, so we can only # know the plan now (at the end). if not self._plan_written: print("1..{0}".format(self.combined_line_number), file=self.stream) self._plan_written = True return if self.combined: combined_file = "testresults.tap" if self.outdir: combined_file = os.path.join(self.outdir, combined_file) with open(combined_file, "w") as out_file: self._write_tap_version(out_file) if self.plan is not None: print("1..{0}".format(self.plan), file=out_file) for test_case in self.combined_test_cases_seen: self.generate_tap_report( test_case, self._test_cases[test_case], out_file ) if self.plan is None: print("1..{0}".format(self.combined_line_number), file=out_file) else: for test_case, tap_lines in self._test_cases.items(): with open(self._get_tap_file_path(test_case), "w") as out_file: self._write_tap_version(out_file) self.generate_tap_report(test_case, tap_lines, out_file) def generate_tap_report(self, test_case, tap_lines, out_file): self._write_test_case_header(test_case, out_file) for tap_line in tap_lines: print(tap_line, file=out_file) # For combined results, the plan is only output once after # all the test cases complete. if not self.combined: print("1..{0}".format(len(tap_lines)), file=out_file) def _write_tap_version(self, filename): """Write a Version 13 TAP row. ``filename`` can be a filename or a stream. """ if ENABLE_VERSION_13: print("TAP version 13", file=filename) def _write_plan(self, stream): """Write the plan line to the stream. If we have a plan and have not yet written it out, write it to the given stream. """ if self.plan is not None: if not self._plan_written: print("1..{0}".format(self.plan), file=stream) self._plan_written = True def _write_test_case_header(self, test_case, stream): print("# TAP results for {test_case}".format(test_case=test_case), file=stream) def _get_tap_file_path(self, test_case): """Get the TAP output file path for the test case.""" sanitized_test_case = test_case.translate(self._sanitized_table) tap_file = sanitized_test_case + ".tap" if self.outdir: return os.path.join(self.outdir, tap_file) return tap_file ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1640756615.6986966 tap.py-3.1/tap.py.egg-info/0000755000076500000240000000000000000000000014326 5ustar00mattstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/PKG-INFO0000644000076500000240000001631600000000000015432 0ustar00mattstaffMetadata-Version: 2.1 Name: tap.py Version: 3.1 Summary: Test Anything Protocol (TAP) tools Home-page: https://github.com/python-tap/tappy Author: Matt Layman Author-email: matthewlayman@gmail.com License: BSD Keywords: TAP,unittest Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Testing Provides-Extra: yaml License-File: LICENSE tappy is a set of tools for working with the `Test Anything Protocol (TAP) `_, a line based test protocol for recording test data in a standard way. Follow tappy development on `GitHub `_. Developer documentation is on `Read the Docs `_. Releases ======== Version 3.1, Released December 29, 2021 --------------------------------------- * Add support for Python 3.10. * Add support for Python 3.9. * Add support for Python 3.8. * Drop support for Python 3.5 (it is end-of-life). * Fix parsing of multi-line strings in YAML blocks (#111) * Remove unmaintained i18n support. Version 3.0, Released January 10, 2020 -------------------------------------- * Drop support for Python 2 (it is end-of-life). * Add support for subtests. * Run a test suite with ``python -m tap``. * Discontinue use of Pipenv for managing development. Version 2.6.2, Released October 20, 2019 ---------------------------------------- * Fix bug in streaming mode that would generate tap files when the plan was already set (affected pytest). Version 2.6.1, Released September 17, 2019 ------------------------------------------ * Fix TAP version 13 support from more-itertools behavior change. Version 2.6, Released September 16, 2019 ---------------------------------------- * Add support for Python 3.7. * Drop support for Python 3.4 (it is end-of-life). Version 2.5, Released September 15, 2018 ---------------------------------------- * Add ``set_plan`` to ``Tracker`` which allows producing the ``1..N`` plan line before any tests. * Switch code style to use Black formatting. Version 2.4, Released May 29, 2018 ---------------------------------- * Add support for producing TAP version 13 output to streaming and file reports by including the ``TAP version 13`` line. Version 2.3, Released May 15, 2018 ---------------------------------- * Add optional method to install tappy for YAML support with ``pip install tap.py[yaml]``. * Make tappy version 13 compliant by adding support for parsing YAML blocks. * ``unittest.expectedFailure`` now uses a TODO directive to better align with the specification. Version 2.2, Released January 7, 2018 ------------------------------------- * Add support for Python 3.6. * Drop support for Python 3.3 (it is end-of-life). * Use Pipenv for managing development. * Switch to pytest as the development test runner. Version 2.1, Released September 23, 2016 ---------------------------------------- * Add ``Parser.parse_text`` to parse TAP provided as a string. Version 2.0, Released July 31, 2016 ----------------------------------- * Remove nose plugin. The plugin moved to the ``nose-tap`` distribution. * Remove pytest plugin. The plugin moved to the ``pytest-tap`` distribution. * Remove Pygments syntax highlighting plugin. The plugin was merged upstream directly into the Pygments project and is available without tappy. * Drop support for Python 2.6. Version 1.9, Released March 28, 2016 ------------------------------------ * ``TAPTestRunner`` has a ``set_header`` method to enable or disable test case header ouput in the TAP stream. * Add support for Python 3.5. * Perform continuous integration testing on OS X. * Drop support for Python 3.2. Version 1.8, Released November 30, 2015 --------------------------------------- * The ``tappy`` TAP consumer can read a TAP stream directly from STDIN. * Tracebacks are included as diagnostic output for failures and errors. * The ``tappy`` TAP consumer has an alternative, shorter name of ``tap``. * The pytest plugin now defaults to no output unless provided a flag. Users dependent on the old default behavior can use ``--tap-files`` to achieve the same results. * Translated into Arabic. * Translated into Chinese. * Translated into Japanese. * Translated into Russian. * Perform continuous integration testing on Windows with AppVeyor. * Improve unit test coverage to 100%. Version 1.7, Released August 19, 2015 ------------------------------------- * Provide a plugin to integrate with pytest. * Document some viable alternatives to tappy. * Translated into German. * Translated into Portuguese. Version 1.6, Released June 18, 2015 ----------------------------------- * ``TAPTestRunner`` has a ``set_stream`` method to stream all TAP output directly to an output stream instead of a file. results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-stream`` flag to stream all TAP output directly to an output stream instead of a file. * tappy is now internationalized. It is translated into Dutch, French, Italian, and Spanish. * tappy is available as a Python wheel package, the new Python packaging standard. Version 1.5, Released May 18, 2015 ---------------------------------- * ``TAPTestRunner`` has a ``set_combined`` method to collect all results in a single output file. * The ``nosetests`` plugin has an optional ``--tap-combined`` flag to collect all results in a single output file. * ``TAPTestRunner`` has a ``set_format`` method to specify line format. * The ``nosetests`` plugin has an optional ``--tap-format`` flag to specify line format. Version 1.4, Released April 4, 2015 ----------------------------------- * Update ``setup.py`` to support Debian packaging. Include man page. Version 1.3, Released January 9, 2015 ------------------------------------- * The ``tappy`` command line tool is available as a TAP consumer. * The ``Parser`` and ``Loader`` are available as APIs for programmatic handling of TAP files and data. Version 1.2, Released December 21, 2014 --------------------------------------- * Provide a syntax highlighter for Pygments so any project using Pygments (e.g., Sphinx) can highlight TAP output. Version 1.1, Released October 23, 2014 -------------------------------------- * ``TAPTestRunner`` has a ``set_outdir`` method to specify where to store ``.tap`` files. * The ``nosetests`` plugin has an optional ``--tap-outdir`` flag to specify where to store ``.tap`` files. * tappy has backported support for Python 2.6. * tappy has support for Python 3.2, 3.3, and 3.4. * tappy has support for PyPy. Version 1.0, Released March 16, 2014 ------------------------------------ * Initial release of tappy * ``TAPTestRunner`` - A test runner for ``unittest`` modules that generates TAP files. * Provides a plugin for integrating with **nose**. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/SOURCES.txt0000644000076500000240000000212100000000000016206 0ustar00mattstaffAUTHORS LICENSE MANIFEST.in README.md setup.cfg setup.py docs/Makefile docs/alternatives.rst docs/conf.py docs/consumers.rst docs/contributing.rst docs/highlighter.rst docs/index.rst docs/make.bat docs/producers.rst docs/releases.rst docs/sample_tap.txt docs/tappy.1.rst docs/_static/.keep docs/images/python-tap.png docs/images/stream.gif docs/images/tap.png tap/__init__.py tap/__main__.py tap/adapter.py tap/directive.py tap/formatter.py tap/line.py tap/loader.py tap/main.py tap/parser.py tap/rules.py tap/runner.py tap/tracker.py tap.py.egg-info/PKG-INFO tap.py.egg-info/SOURCES.txt tap.py.egg-info/dependency_links.txt tap.py.egg-info/entry_points.txt tap.py.egg-info/not-zip-safe tap.py.egg-info/requires.txt tap.py.egg-info/top_level.txt tap/tests/__init__.py tap/tests/factory.py tap/tests/run.py tap/tests/test_adapter.py tap/tests/test_directive.py tap/tests/test_formatter.py tap/tests/test_line.py tap/tests/test_loader.py tap/tests/test_main.py tap/tests/test_parser.py tap/tests/test_result.py tap/tests/test_rules.py tap/tests/test_runner.py tap/tests/test_tracker.py tap/tests/testcase.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/dependency_links.txt0000644000076500000240000000000100000000000020374 0ustar00mattstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/entry_points.txt0000644000076500000240000000007500000000000017626 0ustar00mattstaff[console_scripts] tap = tap.main:main tappy = tap.main:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1515294911.0 tap.py-3.1/tap.py.egg-info/not-zip-safe0000644000076500000240000000000100000000000016554 0ustar00mattstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/requires.txt0000644000076500000240000000004300000000000016723 0ustar00mattstaff [yaml] more-itertools PyYAML>=5.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1640756615.0 tap.py-3.1/tap.py.egg-info/top_level.txt0000644000076500000240000000000400000000000017052 0ustar00mattstafftap