pax_global_header00006660000000000000000000000064137266166140014526gustar00rootroot0000000000000052 comment=ce0656b3ff8026ba24e0ca205916814048a8c920 parse_type-0.5.6/000077500000000000000000000000001372661661400137115ustar00rootroot00000000000000parse_type-0.5.6/.bumpversion.cfg000066400000000000000000000002311372661661400170150ustar00rootroot00000000000000[bumpversion] current_version = 0.5.6 files = setup.py parse_type/__init__.py .bumpversion.cfg pytest.ini commit = False tag = False allow_dirty = True parse_type-0.5.6/.coveragerc000066400000000000000000000021741372661661400160360ustar00rootroot00000000000000# ========================================================================= # COVERAGE CONFIGURATION FILE: .coveragerc # ========================================================================= # LANGUAGE: Python # SEE ALSO: # * http://nedbatchelder.com/code/coverage/ # * http://nedbatchelder.com/code/coverage/config.html # ========================================================================= [run] # data_file = .coverage source = parse_type branch = True parallel = True omit = mock.py, ez_setup.py, distribute.py [report] ignore_errors = True show_missing = True # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ if self\.debug # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if False: if __name__ == .__main__.: [html] directory = build/coverage.html title = Coverage Report: parse_type [xml] output = build/coverage.xml parse_type-0.5.6/.editorconfig000066400000000000000000000010641372661661400163670ustar00rootroot00000000000000# ============================================================================= # EDITOR CONFIGURATION: http://editorconfig.org # ============================================================================= root = true # -- DEFAULT: Unix-style newlines with a newline ending every file. [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{py,rst,ini,txt}] indent_style = space indent_size = 4 [*.feature] indent_style = space indent_size = 2 [**/makefile] indent_style = tab [*.{cmd,bat}] end_of_line = crlf parse_type-0.5.6/.gitignore000066400000000000000000000005001372661661400156740ustar00rootroot00000000000000*.py[cod] # Packages MANIFEST *.egg *.egg-info dist build downloads __pycache__ # Installer logs pip-log.txt Pipfile Pipfile.lock # Unit test / coverage reports .cache/ .eggs/ .pytest_cache/ .tox/ .venv*/ .coverage # -- IDE-RELATED: .idea/ .vscode/ .project .pydevproject # -- EXCLUDE GIT-SUBPROJECTS: /lib/parse/ parse_type-0.5.6/.rosinstall000066400000000000000000000003011372661661400160760ustar00rootroot00000000000000# GIT MULTI-REPO TOOL: wstool # REQUIRES: wstool >= 0.1.17 (better: 0.1.18; not in pypi yet) - git: local-name: lib/parse uri: https://github.com/r1chardj0n3s/parse version: master parse_type-0.5.6/.travis.yml000066400000000000000000000005471372661661400160300ustar00rootroot00000000000000language: python sudo: false python: - "3.7" - "2.7" - "3.8-dev" - "pypy" - "pypy3" # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer matrix: allow_failures: - python: "3.8-dev" - python: "nightly" install: - pip install -U -r py.requirements/ci.travis.txt - python setup.py -q install script: - pytest tests parse_type-0.5.6/CHANGES.txt000066400000000000000000000007751372661661400155330ustar00rootroot00000000000000Version History =============================================================================== Version: 0.4.3 (2018-04-xx, unreleased) ------------------------------------------------------------------------------- CHANGES: * UPDATE: parse-1.8.3 (was: parse-1.8.2) NOTE: ``parse`` module and ``parse_type.parse`` module are now identical. BACKWARD INCOMPATIBLE CHANGES: * RENAMED: type_converter.regex_group_count attribute (was: .group_count) (pull-request review changes of the ``parse`` module). parse_type-0.5.6/LICENSE000066400000000000000000000027201372661661400147170ustar00rootroot00000000000000Copyright (c) 2013-2019, jenisys All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. parse_type-0.5.6/MANIFEST.in000066400000000000000000000007041372661661400154500ustar00rootroot00000000000000include README.rst include LICENSE include .coveragerc include .editorconfig include *.py include *.rst include *.txt include *.ini include *.cfg include *.yaml include bin/invoke* recursive-include bin *.sh *.py *.cmd recursive-include py.requirements *.txt recursive-include tasks *.py *.txt *.rst *.zip recursive-include tests *.py # -- DISABLED: recursive-include docs *.rst *.txt *.py prune .tox prune .venv* parse_type-0.5.6/README.rst000066400000000000000000000224721372661661400154070ustar00rootroot00000000000000=============================================================================== parse_type =============================================================================== .. image:: https://img.shields.io/travis/jenisys/parse_type/master.svg :target: https://travis-ci.org/jenisys/parse_type :alt: Travis CI Build Status .. image:: https://img.shields.io/pypi/v/parse_type.svg :target: https://pypi.python.org/pypi/parse_type :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/parse_type.svg :target: https://pypi.python.org/pypi/parse_type :alt: Downloads .. image:: https://img.shields.io/pypi/l/parse_type.svg :target: https://pypi.python.org/pypi/parse_type/ :alt: License `parse_type`_ extends the `parse`_ module (opposite of `string.format()`_) with the following features: * build type converters for common use cases (enum/mapping, choice) * build a type converter with a cardinality constraint (0..1, 0..*, 1..*) from the type converter with cardinality=1. * compose a type converter from other type converters * an extended parser that supports the CardinalityField naming schema and creates missing type variants (0..1, 0..*, 1..*) from the primary type converter .. _parse_type: http://pypi.python.org/pypi/parse_type .. _parse: http://pypi.python.org/pypi/parse .. _`string.format()`: http://docs.python.org/library/string.html#format-string-syntax Definitions ------------------------------------------------------------------------------- *type converter* A type converter function that converts a textual representation of a value type into instance of this value type. In addition, a type converter function is often annotated with attributes that allows the `parse`_ module to use it in a generic way. A type converter is also called a *parse_type* (a definition used here). *cardinality field* A naming convention for related types that differ in cardinality. A cardinality field is a type name suffix in the format of a field. It allows parse format expression, ala:: "{person:Person}" #< Cardinality: 1 (one; the normal case) "{person:Person?}" #< Cardinality: 0..1 (zero or one = optional) "{persons:Person*}" #< Cardinality: 0..* (zero or more = many0) "{persons:Person+}" #< Cardinality: 1..* (one or more = many) This naming convention mimics the relationship descriptions in UML diagrams. Basic Example ------------------------------------------------------------------------------- Define an own type converter for numbers (integers): .. code-block:: python # -- USE CASE: def parse_number(text): return int(text) parse_number.pattern = r"\d+" # -- REGULAR EXPRESSION pattern for type. This is equivalent to: .. code-block:: python import parse @parse.with_pattern(r"\d+") def parse_number(text): return int(text) assert hasattr(parse_number, "pattern") assert parse_number.pattern == r"\d+" .. code-block:: python # -- USE CASE: Use the type converter with the parse module. schema = "Hello {number:Number}" parser = parse.Parser(schema, dict(Number=parse_number)) result = parser.parse("Hello 42") assert result is not None, "REQUIRE: text matches the schema." assert result["number"] == 42 result = parser.parse("Hello XXX") assert result is None, "MISMATCH: text does not match the schema." .. hint:: The described functionality above is standard functionality of the `parse`_ module. It serves as introduction for the remaining cases. Cardinality ------------------------------------------------------------------------------- Create an type converter for "ManyNumbers" (List, separated with commas) with cardinality "1..* = 1+" (many) from the type converter for a "Number". .. code-block:: python # -- USE CASE: Create new type converter with a cardinality constraint. # CARDINALITY: many := one or more (1..*) from parse import Parser from parse_type import TypeBuilder parse_numbers = TypeBuilder.with_many(parse_number, listsep=",") schema = "List: {numbers:ManyNumbers}" parser = Parser(schema, dict(ManyNumbers=parse_numbers)) result = parser.parse("List: 1, 2, 3") assert result["numbers"] == [1, 2, 3] Create an type converter for an "OptionalNumbers" with cardinality "0..1 = ?" (optional) from the type converter for a "Number". .. code-block:: python # -- USE CASE: Create new type converter with cardinality constraint. # CARDINALITY: optional := zero or one (0..1) from parse import Parser from parse_type import TypeBuilder parse_optional_number = TypeBuilder.with_optional(parse_number) schema = "Optional: {number:OptionalNumber}" parser = Parser(schema, dict(OptionalNumber=parse_optional_number)) result = parser.parse("Optional: 42") assert result["number"] == 42 result = parser.parse("Optional: ") assert result["number"] == None Enumeration (Name-to-Value Mapping) ------------------------------------------------------------------------------- Create an type converter for an "Enumeration" from the description of the mapping as dictionary. .. code-block:: python # -- USE CASE: Create a type converter for an enumeration. from parse import Parser from parse_type import TypeBuilder parse_enum_yesno = TypeBuilder.make_enum({"yes": True, "no": False}) parser = Parser("Answer: {answer:YesNo}", dict(YesNo=parse_enum_yesno)) result = parser.parse("Answer: yes") assert result["answer"] == True Create an type converter for an "Enumeration" from the description of the mapping as an enumeration class (`Python 3.4 enum`_ or the `enum34`_ backport; see also: `PEP-0435`_). .. code-block:: python # -- USE CASE: Create a type converter for enum34 enumeration class. # NOTE: Use Python 3.4 or enum34 backport. from parse import Parser from parse_type import TypeBuilder from enum import Enum class Color(Enum): red = 1 green = 2 blue = 3 parse_enum_color = TypeBuilder.make_enum(Color) parser = Parser("Select: {color:Color}", dict(Color=parse_enum_color)) result = parser.parse("Select: red") assert result["color"] is Color.red .. _`Python 3.4 enum`: http://docs.python.org/3.4/library/enum.html#module-enum .. _enum34: http://pypi.python.org/pypi/enum34 .. _PEP-0435: http://www.python.org/dev/peps/pep-0435 Choice (Name Enumeration) ------------------------------------------------------------------------------- A Choice data type allows to select one of several strings. Create an type converter for an "Choice" list, a list of unique names (as string). .. code-block:: python from parse import Parser from parse_type import TypeBuilder parse_choice_yesno = TypeBuilder.make_choice(["yes", "no"]) schema = "Answer: {answer:ChoiceYesNo}" parser = Parser(schema, dict(ChoiceYesNo=parse_choice_yesno)) result = parser.parse("Answer: yes") assert result["answer"] == "yes" Variant (Type Alternatives) ------------------------------------------------------------------------------- Sometimes you need a type converter that can accept text for multiple type converter alternatives. This is normally called a "variant" (or: union). Create an type converter for an "Variant" type that accepts: * Numbers (positive numbers, as integer) * Color enum values (by name) .. code-block:: python from parse import Parser, with_pattern from parse_type import TypeBuilder from enum import Enum class Color(Enum): red = 1 green = 2 blue = 3 @with_pattern(r"\d+") def parse_number(text): return int(text) # -- MAKE VARIANT: Alternatives of different type converters. parse_color = TypeBuilder.make_enum(Color) parse_variant = TypeBuilder.make_variant([parse_number, parse_color]) schema = "Variant: {variant:Number_or_Color}" parser = Parser(schema, dict(Number_or_Color=parse_variant)) # -- TEST VARIANT: With number, color and mismatch. result = parser.parse("Variant: 42") assert result["variant"] == 42 result = parser.parse("Variant: blue") assert result["variant"] is Color.blue result = parser.parse("Variant: __MISMATCH__") assert not result Extended Parser with CardinalityField support ------------------------------------------------------------------------------- The parser extends the ``parse.Parser`` and adds the following functionality: * supports the CardinalityField naming scheme * automatically creates missing type variants for types with a CardinalityField by using the primary type converter for cardinality=1 * extends the provide type converter dictionary with new type variants. Example: .. code-block:: python # -- USE CASE: Parser with CardinalityField support. # NOTE: Automatically adds missing type variants with CardinalityField part. # USE: parse_number() type converter from above. from parse_type.cfparse import Parser # -- PREPARE: parser, adds missing type variant for cardinality 1..* (many) type_dict = dict(Number=parse_number) schema = "List: {numbers:Number+}" parser = Parser(schema, type_dict) assert "Number+" in type_dict, "Created missing type variant based on: Number" # -- USE: parser. result = parser.parse("List: 1, 2, 3") assert result["numbers"] == [1, 2, 3] parse_type-0.5.6/bin/000077500000000000000000000000001372661661400144615ustar00rootroot00000000000000parse_type-0.5.6/bin/invoke000077500000000000000000000002531372661661400157020ustar00rootroot00000000000000#!/bin/sh #!/bin/bash # RUN INVOKE: From bundled ZIP file. HERE=$(dirname $0) export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" python ${HERE}/../tasks/_vendor/invoke.zip $* parse_type-0.5.6/bin/invoke.cmd000066400000000000000000000003171372661661400164420ustar00rootroot00000000000000@echo off REM RUN INVOKE: From bundled ZIP file. setlocal set HERE=%~dp0 set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" if not defined PYTHON set PYTHON=python %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*" parse_type-0.5.6/bin/make_localpi.py000077500000000000000000000170151372661661400174620ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Utility script to create a pypi-like directory structure (localpi) from a number of Python packages in a directory of the local filesystem. DIRECTORY STRUCTURE (before): +-- downloads/ +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 DIRECTORY STRUCTURE (afterwards): +-- downloads/ +-- simple/ | +-- alice/index.html --> ../../alice-*.* | +-- bob/index.html --> ../../bob-*.* | +-- charly/index.html --> ../../charly-*.* | +-- index.html --> alice/, bob/, ... +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 USAGE EXAMPLE: mkdir -p /tmp/downloads pip install --download=/tmp/downloads argparse Jinja2 make_localpi.py /tmp/downloads pip install --index-url=file:///tmp/downloads/simple argparse Jinja2 ALTERNATIVE: pip install --download=/tmp/downloads argparse Jinja2 pip install --find-links=/tmp/downloads --no-index argparse Jinja2 """ from __future__ import with_statement, print_function from fnmatch import fnmatch import os.path import shutil import sys __author__ = "Jens Engel" __version__ = "0.2" __license__ = "BSD" __copyright__ = "(c) 2013 by Jens Engel" class Package(object): """ Package entity that keeps track of: * one or more versions of this package * one or more archive types """ PATTERNS = [ "*.egg", "*.exe", "*.whl", "*.zip", "*.tar.gz", "*.tar.bz2", "*.7z" ] def __init__(self, filename, name=None): if not name and filename: name = self.get_pkgname(filename) self.name = name self.files = [] if filename: self.files.append(filename) @property def versions(self): versions_info = [ self.get_pkgversion(p) for p in self.files ] return versions_info @classmethod def get_pkgversion(cls, filename): parts = os.path.basename(filename).rsplit("-", 1) version = "" if len(parts) >= 2: version = parts[1] for pattern in cls.PATTERNS: assert pattern.startswith("*") suffix = pattern[1:] if version.endswith(suffix): version = version[:-len(suffix)] break return version @staticmethod def get_pkgname(filename): name = os.path.basename(filename).rsplit("-", 1)[0] if name.startswith("http%3A") or name.startswith("https%3A"): # -- PIP DOWNLOAD-CACHE PACKAGE FILE NAME SCHEMA: pos = name.rfind("%2F") name = name[pos+3:] return name @staticmethod def splitext(filename): fname = os.path.splitext(filename)[0] if fname.endswith(".tar"): fname = os.path.splitext(fname)[0] return fname @classmethod def isa(cls, filename): basename = os.path.basename(filename) if basename.startswith("."): return False for pattern in cls.PATTERNS: if fnmatch(filename, pattern): return True return False def make_index_for(package, index_dir, verbose=True): """ Create an 'index.html' for one package. :param package: Package object to use. :param index_dir: Where 'index.html' should be created. """ index_template = """\ {title}

{title}

""" item_template = '
  • {0}
  • ' index_filename = os.path.join(index_dir, "index.html") if not os.path.isdir(index_dir): os.makedirs(index_dir) parts = [] for pkg_filename in package.files: pkg_name = os.path.basename(pkg_filename) if pkg_name == "index.html": # -- ROOT-INDEX: pkg_name = os.path.basename(os.path.dirname(pkg_filename)) else: pkg_name = package.splitext(pkg_name) pkg_relpath_to = os.path.relpath(pkg_filename, index_dir) parts.append(item_template.format(pkg_name, pkg_relpath_to)) if not parts: print("OOPS: Package %s has no files" % package.name) return if verbose: root_index = not Package.isa(package.files[0]) if root_index: info = "with %d package(s)" % len(package.files) else: package_versions = sorted(set(package.versions)) info = ", ".join(reversed(package_versions)) message = "%-30s %s" % (package.name, info) print(message) with open(index_filename, "w") as f: packages = "\n".join(parts) text = index_template.format(title=package.name, packages=packages) f.write(text.strip()) f.close() def make_package_index(download_dir): """ Create a pypi server like file structure below download directory. :param download_dir: Download directory with packages. EXAMPLE BEFORE: +-- downloads/ +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 EXAMPLE AFTERWARDS: +-- downloads/ +-- simple/ | +-- alice/index.html --> ../../alice-*.* | +-- bob/index.html --> ../../bob-*.* | +-- charly/index.html --> ../../charly-*.* | +-- index.html --> alice/index.html, bob/index.html, ... +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 """ if not os.path.isdir(download_dir): raise ValueError("No such directory: %r" % download_dir) pkg_rootdir = os.path.join(download_dir, "simple") if os.path.isdir(pkg_rootdir): shutil.rmtree(pkg_rootdir, ignore_errors=True) os.mkdir(pkg_rootdir) # -- STEP: Collect all packages. package_map = {} packages = [] for filename in sorted(os.listdir(download_dir)): if not Package.isa(filename): continue pkg_filepath = os.path.join(download_dir, filename) package_name = Package.get_pkgname(pkg_filepath) package = package_map.get(package_name, None) if not package: # -- NEW PACKAGE DETECTED: Store/register package. package = Package(pkg_filepath) package_map[package.name] = package packages.append(package) else: # -- SAME PACKAGE: Collect other variant/version. package.files.append(pkg_filepath) # -- STEP: Make local PYTHON PACKAGE INDEX. root_package = Package(None, "Python Package Index") root_package.files = [ os.path.join(pkg_rootdir, pkg.name, "index.html") for pkg in packages ] make_index_for(root_package, pkg_rootdir) for package in packages: index_dir = os.path.join(pkg_rootdir, package.name) make_index_for(package, index_dir) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": if (len(sys.argv) != 2) or "-h" in sys.argv[1:] or "--help" in sys.argv[1:]: print("USAGE: %s DOWNLOAD_DIR" % os.path.basename(sys.argv[0])) print(__doc__) sys.exit(1) make_package_index(sys.argv[1]) parse_type-0.5.6/bin/project_bootstrap.sh000077500000000000000000000012401372661661400205600ustar00rootroot00000000000000#!/bin/sh # ============================================================================= # BOOTSTRAP PROJECT: Download all requirements # ============================================================================= # test ${PIP_DOWNLOADS_DIR} || mkdir -p ${PIP_DOWNLOADS_DIR} # tox -e init set -e # -- CONFIGURATION: HERE=`dirname $0` TOP="${HERE}/.." : ${PIP_INDEX_URL="http://pypi.python.org/simple"} : ${PIP_DOWNLOAD_DIR:="${TOP}/downloads"} export PIP_INDEX_URL PIP_DOWNLOADS_DIR # -- EXECUTE STEPS: ${HERE}/toxcmd.py mkdir ${PIP_DOWNLOAD_DIR} pip install --download=${PIP_DOWNLOAD_DIR} -r ${TOP}/requirements/all.txt ${HERE}/make_localpi.py ${PIP_DOWNLOAD_DIR} parse_type-0.5.6/bin/toxcmd.py000077500000000000000000000210721372661661400163360ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Provides a command container for additional tox commands, used in "tox.ini". COMMANDS: * copytree * copy * py2to3 REQUIRES: * argparse """ from glob import glob import argparse import inspect import os.path import shutil import sys __author__ = "Jens Engel" __copyright__ = "(c) 2013 by Jens Engel" __license__ = "BSD" # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- VERSION = "0.1.0" FORMATTER_CLASS = argparse.RawDescriptionHelpFormatter # ----------------------------------------------------------------------------- # SUBCOMMAND: copytree # ----------------------------------------------------------------------------- def command_copytree(args): """ Copy one or more source directory(s) below a destination directory. Parts of the destination directory path are created if needed. Similar to the UNIX command: 'cp -R srcdir destdir' """ for srcdir in args.srcdirs: basename = os.path.basename(srcdir) destdir2 = os.path.normpath(os.path.join(args.destdir, basename)) if os.path.exists(destdir2): shutil.rmtree(destdir2) sys.stdout.write("copytree: %s => %s\n" % (srcdir, destdir2)) shutil.copytree(srcdir, destdir2) return 0 def setup_parser_copytree(parser): parser.add_argument("srcdirs", nargs="+", help="Source directory(s)") parser.add_argument("destdir", help="Destination directory") command_copytree.usage = "%(prog)s srcdir... destdir" command_copytree.short = "Copy source dir(s) below a destination directory." command_copytree.setup_parser = setup_parser_copytree # ----------------------------------------------------------------------------- # SUBCOMMAND: copy # ----------------------------------------------------------------------------- def command_copy(args): """ Copy one or more source-files(s) to a destpath (destfile or destdir). Destdir mode is used if: * More than one srcfile is provided * Last parameter ends with a slash ("/"). * Last parameter is an existing directory Destination directory path is created if needed. Similar to the UNIX command: 'cp srcfile... destpath' """ sources = args.sources destpath = args.destpath source_files = [] for file_ in sources: if "*" in file_: selected = glob(file_) source_files.extend(selected) elif os.path.isfile(file_): source_files.append(file_) if destpath.endswith("/") or os.path.isdir(destpath) or len(sources) > 1: # -- DESTDIR-MODE: Last argument is a directory. destdir = destpath else: # -- DESTFILE-MODE: Copy (and rename) one file. assert len(source_files) == 1 destdir = os.path.dirname(destpath) # -- WORK-HORSE: Copy one or more files to destpath. if not os.path.isdir(destdir): sys.stdout.write("copy: Create dir %s\n" % destdir) os.makedirs(destdir) for source in source_files: destname = os.path.join(destdir, os.path.basename(source)) sys.stdout.write("copy: %s => %s\n" % (source, destname)) shutil.copy(source, destname) return 0 def setup_parser_copy(parser): parser.add_argument("sources", nargs="+", help="Source files.") parser.add_argument("destpath", help="Destination path") command_copy.usage = "%(prog)s sources... destpath" command_copy.short = "Copy one or more source files to a destinition." command_copy.setup_parser = setup_parser_copy # ----------------------------------------------------------------------------- # SUBCOMMAND: mkdir # ----------------------------------------------------------------------------- def command_mkdir(args): """ Create a non-existing directory (or more ...). If the directory exists, the step is skipped. Similar to the UNIX command: 'mkdir -p dir' """ errors = 0 for directory in args.dirs: if os.path.exists(directory): if not os.path.isdir(directory): # -- SANITY CHECK: directory exists, but as file... sys.stdout.write("mkdir: %s\n" % directory) sys.stdout.write("ERROR: Exists already, but as file...\n") errors += 1 else: # -- NORMAL CASE: Directory does not exits yet. assert not os.path.isdir(directory) sys.stdout.write("mkdir: %s\n" % directory) os.makedirs(directory) return errors def setup_parser_mkdir(parser): parser.add_argument("dirs", nargs="+", help="Directory(s)") command_mkdir.usage = "%(prog)s dir..." command_mkdir.short = "Create non-existing directory (or more...)." command_mkdir.setup_parser = setup_parser_mkdir # ----------------------------------------------------------------------------- # SUBCOMMAND: py2to3 # ----------------------------------------------------------------------------- def command_py2to3(args): """ Apply '2to3' tool (Python2 to Python3 conversion tool) to Python sources. """ from lib2to3.main import main sys.exit(main("lib2to3.fixes", args=args.sources)) def setup_parser4py2to3(parser): parser.add_argument("sources", nargs="+", help="Source files.") command_py2to3.name = "2to3" command_py2to3.usage = "%(prog)s sources..." command_py2to3.short = "Apply python's 2to3 tool to Python sources." command_py2to3.setup_parser = setup_parser4py2to3 # ----------------------------------------------------------------------------- # COMMAND HELPERS/UTILS: # ----------------------------------------------------------------------------- def discover_commands(): commands = [] for name, func in inspect.getmembers(inspect.getmodule(toxcmd_main)): if name.startswith("__"): continue if name.startswith("command_") and callable(func): command_name0 = name.replace("command_", "") command_name = getattr(func, "name", command_name0) commands.append(Command(command_name, func)) return commands class Command(object): def __init__(self, name, func): assert isinstance(name, basestring) assert callable(func) self.name = name self.func = func self.parser = None def setup_parser(self, command_parser): setup_parser = getattr(self.func, "setup_parser", None) if setup_parser and callable(setup_parser): setup_parser(command_parser) else: command_parser.add_argument("args", nargs="*") @property def usage(self): usage = getattr(self.func, "usage", None) return usage @property def short_description(self): short_description = getattr(self.func, "short", "") return short_description @property def description(self): return inspect.getdoc(self.func) def __call__(self, args): return self.func(args) # ----------------------------------------------------------------------------- # MAIN-COMMAND: # ----------------------------------------------------------------------------- def toxcmd_main(args=None): """Command util with subcommands for tox environments.""" usage = "USAGE: %(prog)s [OPTIONS] COMMAND args..." if args is None: args = sys.argv[1:] # -- STEP: Build command-line parser. parser = argparse.ArgumentParser(description=inspect.getdoc(toxcmd_main), formatter_class=FORMATTER_CLASS) common_parser = parser.add_argument_group("Common options") common_parser.add_argument("--version", action="version", version=VERSION) subparsers = parser.add_subparsers(help="commands") for command in discover_commands(): command_parser = subparsers.add_parser(command.name, usage=command.usage, description=command.description, help=command.short_description, formatter_class=FORMATTER_CLASS) command_parser.set_defaults(func=command) command.setup_parser(command_parser) command.parser = command_parser # -- STEP: Process command-line and run command. options = parser.parse_args(args) command_function = options.func return command_function(options) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(toxcmd_main()) parse_type-0.5.6/invoke.yaml000066400000000000000000000011731372661661400160720ustar00rootroot00000000000000# ===================================================== # INVOKE CONFIGURATION: parse_type # ===================================================== # -- ON WINDOWS: # run: # echo: true # pty: false # shell: C:\Windows\System32\cmd.exe # ===================================================== project: name: parse_type repo: "pypi" # -- TODO: until upload problems are resolved. repo_url: "https://upload.pypi.org/legacy/" tasks: auto_dash_names: false run: echo: true # DISABLED: pty: true cleanup_all: extra_directories: - build - dist - .hypothesis - .pytest_cache parse_type-0.5.6/parse_type/000077500000000000000000000000001372661661400160645ustar00rootroot00000000000000parse_type-0.5.6/parse_type/__init__.py000066400000000000000000000030501372661661400201730ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module extends the :mod:`parse` to build and derive additional parse-types from other, existing types. """ from __future__ import absolute_import from parse_type.cardinality import Cardinality from parse_type.builder import TypeBuilder, build_type_dict __all__ = ["Cardinality", "TypeBuilder", "build_type_dict"] __version__ = "0.5.6" # ----------------------------------------------------------------------------- # Copyright (c) 2012-2020 by Jens Engel (https://github/jenisys/) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/parse_type/builder.py000066400000000000000000000275771372661661400201060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # pylint: disable=missing-docstring r""" Provides support to compose user-defined parse types. Cardinality ------------ It is often useful to constrain how often a data type occurs. This is also called the cardinality of a data type (in a context). The supported cardinality are: * 0..1 zero_or_one, optional: T or None * 0..N zero_or_more, list_of * 1..N one_or_more, list_of (many) .. doctest:: cardinality >>> from parse_type import TypeBuilder >>> from parse import Parser >>> def parse_number(text): ... return int(text) >>> parse_number.pattern = r"\d+" >>> parse_many_numbers = TypeBuilder.with_many(parse_number) >>> more_types = { "Numbers": parse_many_numbers } >>> parser = Parser("List: {numbers:Numbers}", more_types) >>> parser.parse("List: 1, 2, 3") Enumeration Type (Name-to-Value Mappings) ----------------------------------------- An Enumeration data type allows to select one of several enum values by using its name. The converter function returns the selected enum value. .. doctest:: make_enum >>> parse_enum_yesno = TypeBuilder.make_enum({"yes": True, "no": False}) >>> more_types = { "YesNo": parse_enum_yesno } >>> parser = Parser("Answer: {answer:YesNo}", more_types) >>> parser.parse("Answer: yes") Choice (Name Enumerations) ----------------------------- A Choice data type allows to select one of several strings. .. doctest:: make_choice >>> parse_choice_yesno = TypeBuilder.make_choice(["yes", "no"]) >>> more_types = { "ChoiceYesNo": parse_choice_yesno } >>> parser = Parser("Answer: {answer:ChoiceYesNo}", more_types) >>> parser.parse("Answer: yes") """ from __future__ import absolute_import import inspect import re import enum from parse_type.cardinality import pattern_group_count, \ Cardinality, TypeBuilder as CardinalityTypeBuilder __all__ = ["TypeBuilder", "build_type_dict", "parse_anything"] class TypeBuilder(CardinalityTypeBuilder): """ Provides a utility class to build type-converters (parse_types) for the :mod:`parse` module. """ default_strict = True default_re_opts = (re.IGNORECASE | re.DOTALL) @classmethod def make_list(cls, item_converter=None, listsep=','): """ Create a type converter for a list of items (many := 1..*). The parser accepts anything and the converter needs to fail on errors. :param item_converter: Type converter for an item. :param listsep: List separator to use (as string). :return: Type converter function object for the list. """ if not item_converter: item_converter = parse_anything return cls.with_cardinality(Cardinality.many, item_converter, pattern=cls.anything_pattern, listsep=listsep) @staticmethod def make_enum(enum_mappings): """ Creates a type converter for an enumeration or text-to-value mapping. :param enum_mappings: Defines enumeration names and values. :return: Type converter function object for the enum/mapping. """ if (inspect.isclass(enum_mappings) and issubclass(enum_mappings, enum.Enum)): enum_class = enum_mappings enum_mappings = enum_class.__members__ def convert_enum(text): if text not in convert_enum.mappings: text = text.lower() # REQUIRED-BY: parse re.IGNORECASE return convert_enum.mappings[text] #< text.lower() ??? convert_enum.pattern = r"|".join(enum_mappings.keys()) convert_enum.mappings = enum_mappings return convert_enum @staticmethod def _normalize_choices(choices, transform): assert transform is None or callable(transform) if transform: choices = [transform(value) for value in choices] else: choices = list(choices) return choices @classmethod def make_choice(cls, choices, transform=None, strict=None): """ Creates a type-converter function to select one from a list of strings. The type-converter function returns the selected choice_text. The :param:`transform()` function is applied in the type converter. It can be used to enforce the case (because parser uses re.IGNORECASE). :param choices: List of strings as choice. :param transform: Optional, initial transform function for parsed text. :return: Type converter function object for this choices. """ # -- NOTE: Parser uses re.IGNORECASE flag # => transform may enforce case. choices = cls._normalize_choices(choices, transform) if strict is None: strict = cls.default_strict def convert_choice(text): if transform: text = transform(text) if strict and text not in convert_choice.choices: values = ", ".join(convert_choice.choices) raise ValueError("%s not in: %s" % (text, values)) return text convert_choice.pattern = r"|".join(choices) convert_choice.choices = choices return convert_choice @classmethod def make_choice2(cls, choices, transform=None, strict=None): """ Creates a type converter to select one item from a list of strings. The type converter function returns a tuple (index, choice_text). :param choices: List of strings as choice. :param transform: Optional, initial transform function for parsed text. :return: Type converter function object for this choices. """ choices = cls._normalize_choices(choices, transform) if strict is None: strict = cls.default_strict def convert_choice2(text): if transform: text = transform(text) if strict and text not in convert_choice2.choices: values = ", ".join(convert_choice2.choices) raise ValueError("%s not in: %s" % (text, values)) index = convert_choice2.choices.index(text) return index, text convert_choice2.pattern = r"|".join(choices) convert_choice2.choices = choices return convert_choice2 @classmethod def make_variant(cls, converters, re_opts=None, compiled=False, strict=True): """ Creates a type converter for a number of type converter alternatives. The first matching type converter is used. REQUIRES: type_converter.pattern attribute :param converters: List of type converters as alternatives. :param re_opts: Regular expression options zu use (=default_re_opts). :param compiled: Use compiled regexp matcher, if true (=False). :param strict: Enable assertion checks. :return: Type converter function object. .. note:: Works only with named fields in :class:`parse.Parser`. Parser needs group_index delta for unnamed/fixed fields. This is not supported for user-defined types. Otherwise, you need to use :class:`parse_type.parse.Parser` (patched version of the :mod:`parse` module). """ # -- NOTE: Uses double-dispatch with regex pattern rematch because # match is not passed through to primary type converter. assert converters, "REQUIRE: Non-empty list." if len(converters) == 1: return converters[0] if re_opts is None: re_opts = cls.default_re_opts pattern = r")|(".join([tc.pattern for tc in converters]) pattern = r"("+ pattern + ")" group_count = len(converters) for converter in converters: group_count += pattern_group_count(converter.pattern) if compiled: convert_variant = cls.__create_convert_variant_compiled(converters, re_opts, strict) else: convert_variant = cls.__create_convert_variant(re_opts, strict) convert_variant.pattern = pattern convert_variant.converters = tuple(converters) convert_variant.regex_group_count = group_count return convert_variant @staticmethod def __create_convert_variant(re_opts, strict): # -- USE: Regular expression pattern (compiled on use). def convert_variant(text, m=None): # pylint: disable=invalid-name, unused-argument, missing-docstring for converter in convert_variant.converters: if re.match(converter.pattern, text, re_opts): return converter(text) # -- pragma: no cover assert not strict, "OOPS-VARIANT-MISMATCH: %s" % text return None return convert_variant @staticmethod def __create_convert_variant_compiled(converters, re_opts, strict): # -- USE: Compiled regular expression matcher. for converter in converters: matcher = getattr(converter, "matcher", None) if not matcher: converter.matcher = re.compile(converter.pattern, re_opts) def convert_variant(text, m=None): # pylint: disable=invalid-name, unused-argument, missing-docstring for converter in convert_variant.converters: if converter.matcher.match(text): return converter(text) # -- pragma: no cover assert not strict, "OOPS-VARIANT-MISMATCH: %s" % text return None return convert_variant def build_type_dict(converters): """ Builds type dictionary for user-defined type converters, used by :mod:`parse` module. This requires that each type converter has a "name" attribute. :param converters: List of type converters (parse_types) :return: Type converter dictionary """ more_types = {} for converter in converters: assert callable(converter) more_types[converter.name] = converter return more_types # ----------------------------------------------------------------------------- # COMMON TYPE CONVERTERS # ----------------------------------------------------------------------------- def parse_anything(text, match=None, match_start=0): """ Provides a generic type converter that accepts anything and returns the text (unchanged). :param text: Text to convert (as string). :return: Same text (as string). """ # pylint: disable=unused-argument return text parse_anything.pattern = TypeBuilder.anything_pattern # ----------------------------------------------------------------------------- # Copyright (c) 2012-2020 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/parse_type/cardinality.py000066400000000000000000000205601372661661400207440ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module simplifies to build parse types and regular expressions for a data type with the specified cardinality. """ # -- USE: enum34 from __future__ import absolute_import from enum import Enum # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def pattern_group_count(pattern): """Count the pattern-groups within a regex-pattern (as text).""" return pattern.replace(r"\(", "").count("(") # ----------------------------------------------------------------------------- # CLASS: Cardinality (Enum Class) # ----------------------------------------------------------------------------- class Cardinality(Enum): """Cardinality enumeration class to simplify building regular expression patterns for a data type with the specified cardinality. """ # pylint: disable=bad-whitespace __order__ = "one, zero_or_one, zero_or_more, one_or_more" one = (None, 0) zero_or_one = (r"(%s)?", 1) # SCHEMA: pattern zero_or_more = (r"(%s)?(\s*%s\s*(%s))*", 3) # SCHEMA: pattern sep pattern one_or_more = (r"(%s)(\s*%s\s*(%s))*", 3) # SCHEMA: pattern sep pattern # -- ALIASES: optional = zero_or_one many0 = zero_or_more many = one_or_more def __init__(self, schema, group_count=0): self.schema = schema self.group_count = group_count #< Number of match groups. def is_many(self): """Checks for a more general interpretation of "many". :return: True, if Cardinality.zero_or_more or Cardinality.one_or_more. """ return ((self is Cardinality.zero_or_more) or (self is Cardinality.one_or_more)) def make_pattern(self, pattern, listsep=','): """Make pattern for a data type with the specified cardinality. .. code-block:: python yes_no_pattern = r"yes|no" many_yes_no = Cardinality.one_or_more.make_pattern(yes_no_pattern) :param pattern: Regular expression for type (as string). :param listsep: List separator for multiple items (as string, optional) :return: Regular expression pattern for type with cardinality. """ if self is Cardinality.one: return pattern elif self is Cardinality.zero_or_one: return self.schema % pattern # -- OTHERWISE: return self.schema % (pattern, listsep, pattern) def compute_group_count(self, pattern): """Compute the number of regexp match groups when the pattern is provided to the :func:`Cardinality.make_pattern()` method. :param pattern: Item regexp pattern (as string). :return: Number of regexp match groups in the cardinality pattern. """ group_count = self.group_count pattern_repeated = 1 if self.is_many(): pattern_repeated = 2 return group_count + pattern_repeated * pattern_group_count(pattern) # ----------------------------------------------------------------------------- # CLASS: TypeBuilder # ----------------------------------------------------------------------------- class TypeBuilder(object): """Provides a utility class to build type-converters (parse_types) for parse. It supports to build new type-converters for different cardinality based on the type-converter for cardinality one. """ anything_pattern = r".+?" default_pattern = anything_pattern @classmethod def with_cardinality(cls, cardinality, converter, pattern=None, listsep=','): """Creates a type converter for the specified cardinality by using the type converter for T. :param cardinality: Cardinality to use (0..1, 0..*, 1..*). :param converter: Type converter (function) for data type T. :param pattern: Regexp pattern for an item (=converter.pattern). :return: type-converter for optional (T or None). """ if cardinality is Cardinality.one: return converter # -- NORMAL-CASE builder_func = getattr(cls, "with_%s" % cardinality.name) if cardinality is Cardinality.zero_or_one: return builder_func(converter, pattern) # -- MANY CASE: 0..*, 1..* return builder_func(converter, pattern, listsep=listsep) @classmethod def with_zero_or_one(cls, converter, pattern=None): """Creates a type converter for a T with 0..1 times by using the type converter for one item of T. :param converter: Type converter (function) for data type T. :param pattern: Regexp pattern for an item (=converter.pattern). :return: type-converter for optional (T or None). """ cardinality = Cardinality.zero_or_one if not pattern: pattern = getattr(converter, "pattern", cls.default_pattern) optional_pattern = cardinality.make_pattern(pattern) group_count = cardinality.compute_group_count(pattern) def convert_optional(text, m=None): # pylint: disable=invalid-name, unused-argument, missing-docstring if text: text = text.strip() if not text: return None return converter(text) convert_optional.pattern = optional_pattern convert_optional.regex_group_count = group_count return convert_optional @classmethod def with_zero_or_more(cls, converter, pattern=None, listsep=","): """Creates a type converter function for a list with 0..N items by using the type converter for one item of T. :param converter: Type converter (function) for data type T. :param pattern: Regexp pattern for an item (=converter.pattern). :param listsep: Optional list separator between items (default: ',') :return: type-converter for list """ cardinality = Cardinality.zero_or_more if not pattern: pattern = getattr(converter, "pattern", cls.default_pattern) many0_pattern = cardinality.make_pattern(pattern, listsep) group_count = cardinality.compute_group_count(pattern) def convert_list0(text, m=None): # pylint: disable=invalid-name, unused-argument, missing-docstring if text: text = text.strip() if not text: return [] return [converter(part.strip()) for part in text.split(listsep)] convert_list0.pattern = many0_pattern # OLD convert_list0.group_count = group_count convert_list0.regex_group_count = group_count return convert_list0 @classmethod def with_one_or_more(cls, converter, pattern=None, listsep=","): """Creates a type converter function for a list with 1..N items by using the type converter for one item of T. :param converter: Type converter (function) for data type T. :param pattern: Regexp pattern for an item (=converter.pattern). :param listsep: Optional list separator between items (default: ',') :return: Type converter for list """ cardinality = Cardinality.one_or_more if not pattern: pattern = getattr(converter, "pattern", cls.default_pattern) many_pattern = cardinality.make_pattern(pattern, listsep) group_count = cardinality.compute_group_count(pattern) def convert_list(text, m=None): # pylint: disable=invalid-name, unused-argument, missing-docstring return [converter(part.strip()) for part in text.split(listsep)] convert_list.pattern = many_pattern # OLD: convert_list.group_count = group_count convert_list.regex_group_count = group_count return convert_list # -- ALIAS METHODS: @classmethod def with_optional(cls, converter, pattern=None): """Alias for :py:meth:`with_zero_or_one()` method.""" return cls.with_zero_or_one(converter, pattern) @classmethod def with_many(cls, converter, pattern=None, listsep=','): """Alias for :py:meth:`with_one_or_more()` method.""" return cls.with_one_or_more(converter, pattern, listsep) @classmethod def with_many0(cls, converter, pattern=None, listsep=','): """Alias for :py:meth:`with_zero_or_more()` method.""" return cls.with_zero_or_more(converter, pattern, listsep) parse_type-0.5.6/parse_type/cardinality_field.py000066400000000000000000000152561372661661400221150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Provides support for cardinality fields. A cardinality field is a type suffix for parse format expression, ala: "{person:Person?}" #< Cardinality: 0..1 = zero or one = optional "{persons:Person*}" #< Cardinality: 0..* = zero or more = many0 "{persons:Person+}" #< Cardinality: 1..* = one or more = many """ from __future__ import absolute_import import six from parse_type.cardinality import Cardinality, TypeBuilder class MissingTypeError(KeyError): # pylint: disable=missing-docstring pass # ----------------------------------------------------------------------------- # CLASS: Cardinality (Field Part) # ----------------------------------------------------------------------------- class CardinalityField(object): """Cardinality field for parse format expression, ala: "{person:Person?}" #< Cardinality: 0..1 = zero or one = optional "{persons:Person*}" #< Cardinality: 0..* = zero or more = many0 "{persons:Person+}" #< Cardinality: 1..* = one or more = many """ # -- MAPPING SUPPORT: pattern_chars = "?*+" from_char_map = { '?': Cardinality.zero_or_one, '*': Cardinality.zero_or_more, '+': Cardinality.one_or_more, } to_char_map = dict([(value, key) for key, value in from_char_map.items()]) @classmethod def matches_type(cls, type_name): """Checks if a type name uses the CardinalityField naming scheme. :param type_name: Type name to check (as string). :return: True, if type name has CardinalityField name suffix. """ return type_name and type_name[-1] in CardinalityField.pattern_chars @classmethod def split_type(cls, type_name): """Split type of a type name with CardinalityField suffix into its parts. :param type_name: Type name (as string). :return: Tuple (type_basename, cardinality) """ if cls.matches_type(type_name): basename = type_name[:-1] cardinality = cls.from_char_map[type_name[-1]] else: # -- ASSUME: Cardinality.one cardinality = Cardinality.one basename = type_name return (basename, cardinality) @classmethod def make_type(cls, basename, cardinality): """Build new type name according to CardinalityField naming scheme. :param basename: Type basename of primary type (as string). :param cardinality: Cardinality of the new type (as Cardinality item). :return: Type name with CardinalityField suffix (if needed) """ if cardinality is Cardinality.one: # -- POSTCONDITION: assert not cls.make_type(type_name) return basename # -- NORMAL CASE: type with CardinalityField suffix. type_name = "%s%s" % (basename, cls.to_char_map[cardinality]) # -- POSTCONDITION: assert cls.make_type(type_name) return type_name # ----------------------------------------------------------------------------- # CLASS: CardinalityFieldTypeBuilder # ----------------------------------------------------------------------------- class CardinalityFieldTypeBuilder(object): """Utility class to create type converters based on: * the CardinalityField naming scheme and * type converter for cardinality=1 """ listsep = ',' @classmethod def create_type_variant(cls, type_name, type_converter): r"""Create type variants for types with a cardinality field. The new type converters are based on the type converter with cardinality=1. .. code-block:: python import parse @parse.with_pattern(r'\d+') def parse_number(text): return int(text) new_type = CardinalityFieldTypeBuilder.create_type_variant( "Number+", parse_number) new_type = CardinalityFieldTypeBuilder.create_type_variant( "Number+", dict(Number=parse_number)) :param type_name: Type name with cardinality field suffix. :param type_converter: Type converter or type dictionary. :return: Type converter variant (function). :raises: ValueError, if type_name does not end with CardinalityField :raises: MissingTypeError, if type_converter is missing in type_dict """ assert isinstance(type_name, six.string_types) if not CardinalityField.matches_type(type_name): message = "type_name='%s' has no CardinalityField" % type_name raise ValueError(message) primary_name, cardinality = CardinalityField.split_type(type_name) if isinstance(type_converter, dict): type_dict = type_converter type_converter = type_dict.get(primary_name, None) if not type_converter: raise MissingTypeError(primary_name) assert callable(type_converter) type_variant = TypeBuilder.with_cardinality(cardinality, type_converter, listsep=cls.listsep) type_variant.name = type_name return type_variant @classmethod def create_type_variants(cls, type_names, type_dict): """Create type variants for types with a cardinality field. The new type converters are based on the type converter with cardinality=1. .. code-block:: python # -- USE: parse_number() type converter function. new_types = CardinalityFieldTypeBuilder.create_type_variants( ["Number?", "Number+"], dict(Number=parse_number)) :param type_names: List of type names with cardinality field suffix. :param type_dict: Type dictionary with named type converters. :return: Type dictionary with type converter variants. """ type_variant_dict = {} for type_name in type_names: type_variant = cls.create_type_variant(type_name, type_dict) type_variant_dict[type_name] = type_variant return type_variant_dict # MAYBE: Check if really needed. @classmethod def create_missing_type_variants(cls, type_names, type_dict): """Create missing type variants for types with a cardinality field. :param type_names: List of type names with cardinality field suffix. :param type_dict: Type dictionary with named type converters. :return: Type dictionary with missing type converter variants. """ missing_type_names = [name for name in type_names if name not in type_dict] return cls.create_type_variants(missing_type_names, type_dict) parse_type-0.5.6/parse_type/cfparse.py000066400000000000000000000072751372661661400200740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Provides an extended :class:`parse.Parser` class that supports the cardinality fields in (user-defined) types. """ from __future__ import absolute_import import logging import parse from .cardinality_field import CardinalityField, CardinalityFieldTypeBuilder from .parse_util import FieldParser log = logging.getLogger(__name__) # pylint: disable=invalid-name class Parser(parse.Parser): """Provides an extended :class:`parse.Parser` with cardinality field support. A cardinality field is a type suffix for parse format expression, ala: "... {person:Person?} ..." -- OPTIONAL: Cardinality zero or one, 0..1 "... {persons:Person*} ..." -- MANY0: Cardinality zero or more, 0.. "... {persons:Person+} ..." -- MANY: Cardinality one or more, 1.. When the primary type converter for cardinality=1 is provided, the type variants for the other cardinality cases can be derived from it. This parser class automatically creates missing type variants for types with a cardinality field and passes the extended type dictionary to its base class. """ # -- TYPE-BUILDER: For missing types in Fields with CardinalityField part. type_builder = CardinalityFieldTypeBuilder def __init__(self, schema, extra_types=None, case_sensitive=False, type_builder=None): """Creates a parser with CardinalityField part support. :param schema: Parse schema (or format) for parser (as string). :param extra_types: Type dictionary with type converters (or None). :param case_sensitive: Indicates if case-sensitive regexp are used. :param type_builder: Type builder to use for missing types. """ if extra_types is None: extra_types = {} missing = self.create_missing_types(schema, extra_types, type_builder) if missing: # pylint: disable=logging-not-lazy log.debug("MISSING TYPES: %s" % ",".join(missing.keys())) extra_types.update(missing) # -- FINALLY: Delegate to base class. super(Parser, self).__init__(schema, extra_types, case_sensitive=case_sensitive) @classmethod def create_missing_types(cls, schema, type_dict, type_builder=None): """Creates missing types for fields with a CardinalityField part. It is assumed that the primary type converter for cardinality=1 is registered in the type dictionary. :param schema: Parse schema (or format) for parser (as string). :param type_dict: Type dictionary with type converters. :param type_builder: Type builder to use for missing types. :return: Type dictionary with missing types. Empty, if none. :raises: MissingTypeError, if a primary type converter with cardinality=1 is missing. """ if not type_builder: type_builder = cls.type_builder missing = cls.extract_missing_special_type_names(schema, type_dict) return type_builder.create_type_variants(missing, type_dict) @staticmethod def extract_missing_special_type_names(schema, type_dict): # pylint: disable=invalid-name """Extract the type names for fields with CardinalityField part. Selects only the missing type names that are not in the type dictionary. :param schema: Parse schema to use (as string). :param type_dict: Type dictionary with type converters. :return: Generator with missing type names (as string). """ for name in FieldParser.extract_types(schema): if CardinalityField.matches_type(name) and (name not in type_dict): yield name parse_type-0.5.6/parse_type/parse.py000066400000000000000000001457601372661661400175650ustar00rootroot00000000000000# -*- coding: UTF-8 -*- # BASED-ON: https://github.com/r1chardj0n3s/parse/parse.py # VERSION: parse 1.18.0 # Same as original parse modules. # # pylint: disable=line-too-long, invalid-name, too-many-locals, too-many-arguments # pylint: disable=redefined-builtin, too-few-public-methods, no-else-return # pylint: disable=unused-variable, no-self-use, missing-docstring # pylint: disable=unused-argument, unused-variable # pylint: disable=too-many-branches, too-many-statements # pylint: disable=all # # -- ORIGINAL-CODE STARTS-HERE ------------------------------------------------ r'''Parse strings using a specification based on the Python format() syntax. ``parse()`` is the opposite of ``format()`` The module is set up to only export ``parse()``, ``search()``, ``findall()``, and ``with_pattern()`` when ``import \*`` is used: >>> from parse import * From there it's a simple thing to parse a string: .. code-block:: pycon >>> parse("It's {}, I love it!", "It's spam, I love it!") >>> _[0] 'spam' Or to search a string for some pattern: .. code-block:: pycon >>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n') Or find all the occurrences of some pattern in a string: .. code-block:: pycon >>> ''.join(r[0] for r in findall(">{}<", "

    the bold text

    ")) 'the bold text' If you're going to use the same pattern to match lots of strings you can compile it once: .. code-block:: pycon >>> from parse import compile >>> p = compile("It's {}, I love it!") >>> print(p) >>> p.parse("It's spam, I love it!") ("compile" is not exported for ``import *`` usage as it would override the built-in ``compile()`` function) The default behaviour is to match strings case insensitively. You may match with case by specifying `case_sensitive=True`: .. code-block:: pycon >>> parse('SPAM', 'spam', case_sensitive=True) is None True Format Syntax ------------- A basic version of the `Format String Syntax`_ is supported with anonymous (fixed-position), named and formatted fields:: {[field name]:[format spec]} Field names must be a valid Python identifiers, including dotted names; element indexes imply dictionaries (see below for example). Numbered fields are also not supported: the result of parsing will include the parsed fields in the order they are parsed. The conversion of fields to types other than strings is done based on the type in the format specification, which mirrors the ``format()`` behaviour. There are no "!" field conversions like ``format()`` has. Some simple parse() format string examples: .. code-block:: pycon >>> parse("Bring me a {}", "Bring me a shrubbery") >>> r = parse("The {} who say {}", "The knights who say Ni!") >>> print(r) >>> print(r.fixed) ('knights', 'Ni!') >>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade") >>> print(r) >>> print(r.named) {'item': 'hand grenade'} >>> print(r['item']) hand grenade >>> 'item' in r True Note that `in` only works if you have named fields. Dotted names and indexes are possible though the application must make additional sense of the result: .. code-block:: pycon >>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!") >>> print(r) >>> print(r.named) {'food.type': 'spam'} >>> print(r['food.type']) spam >>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!") >>> print(r) >>> print(r['quest']) {'name': 'to seek the holy grail!'} >>> print(r['quest']['name']) to seek the holy grail! If the text you're matching has braces in it you can match those by including a double-brace ``{{`` or ``}}`` in your format string, just like format() does. Format Specification -------------------- Most often a straight format-less ``{}`` will suffice where a more complex format specification might have been used. Most of `format()`'s `Format Specification Mini-Language`_ is supported: [[fill]align][0][width][.precision][type] The differences between `parse()` and `format()` are: - The align operators will cause spaces (or specified fill character) to be stripped from the parsed value. The width is not enforced; it just indicates there may be whitespace or "0"s to strip. - Numeric parsing will automatically handle a "0b", "0o" or "0x" prefix. That is, the "#" format character is handled automatically by d, b, o and x formats. For "d" any will be accepted, but for the others the correct prefix must be present if at all. - Numeric sign is handled automatically. - The thousands separator is handled automatically if the "n" type is used. - The types supported are a slightly different mix to the format() types. Some format() types come directly over: "d", "n", "%", "f", "e", "b", "o" and "x". In addition some regular expression character group types "D", "w", "W", "s" and "S" are also available. - The "e" and "g" types are case-insensitive so there is not need for the "E" or "G" types. The "e" type handles Fortran formatted numbers (no leading 0 before the decimal point). ===== =========================================== ======== Type Characters Matched Output ===== =========================================== ======== l Letters (ASCII) str w Letters, numbers and underscore str W Not letters, numbers and underscore str s Whitespace str S Non-whitespace str d Digits (effectively integer numbers) int D Non-digit str n Numbers with thousands separators (, or .) int % Percentage (converted to value/100.0) float f Fixed-point numbers float F Decimal numbers Decimal e Floating-point numbers with exponent float e.g. 1.1e-10, NAN (all case insensitive) g General number format (either d, f or e) float b Binary numbers int o Octal numbers int x Hexadecimal numbers (lower and upper case) int ti ISO 8601 format date/time datetime e.g. 1972-01-20T10:21:36Z ("T" and "Z" optional) te RFC2822 e-mail format date/time datetime e.g. Mon, 20 Jan 1972 10:21:36 +1000 tg Global (day/month) format date/time datetime e.g. 20/1/1972 10:21:36 AM +1:00 ta US (month/day) format date/time datetime e.g. 1/20/1972 10:21:36 PM +10:30 tc ctime() format date/time datetime e.g. Sun Sep 16 01:03:52 1973 th HTTP log format date/time datetime e.g. 21/Nov/2011:00:07:11 +0000 ts Linux system log format date/time datetime e.g. Nov 9 03:37:44 tt Time time e.g. 10:21:36 PM -5:30 ===== =========================================== ======== Some examples of typed parsing with ``None`` returned if the typing does not match: .. code-block:: pycon >>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...') >>> parse('Our {:d} {:w} are...', 'Our three weapons are...') >>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM') And messing about with alignment: .. code-block:: pycon >>> parse('with {:>} herring', 'with a herring') >>> parse('spam {:^} spam', 'spam lovely spam') Note that the "center" alignment does not test to make sure the value is centered - it just strips leading and trailing whitespace. Width and precision may be used to restrict the size of matched text from the input. Width specifies a minimum size and precision specifies a maximum. For example: .. code-block:: pycon >>> parse('{:.2}{:.2}', 'look') # specifying precision >>> parse('{:4}{:4}', 'look at that') # specifying width >>> parse('{:4}{:.4}', 'look at that') # specifying both >>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers Some notes for the date and time types: - the presence of the time part is optional (including ISO 8601, starting at the "T"). A full datetime object will always be returned; the time will be set to 00:00:00. You may also specify a time without seconds. - when a seconds amount is present in the input fractions will be parsed to give microseconds. - except in ISO 8601 the day and month digits may be 0-padded. - the date separator for the tg and ta formats may be "-" or "/". - named months (abbreviations or full names) may be used in the ta and tg formats in place of numeric months. - as per RFC 2822 the e-mail format may omit the day (and comma), and the seconds but nothing else. - hours greater than 12 will be happily accepted. - the AM/PM are optional, and if PM is found then 12 hours will be added to the datetime object's hours amount - even if the hour is greater than 12 (for consistency.) - in ISO 8601 the "Z" (UTC) timezone part may be a numeric offset - timezones are specified as "+HH:MM" or "-HH:MM". The hour may be one or two digits (0-padded is OK.) Also, the ":" is optional. - the timezone is optional in all except the e-mail format (it defaults to UTC.) - named timezones are not handled yet. Note: attempting to match too many datetime fields in a single parse() will currently result in a resource allocation issue. A TooManyFields exception will be raised in this instance. The current limit is about 15. It is hoped that this limit will be removed one day. .. _`Format String Syntax`: http://docs.python.org/library/string.html#format-string-syntax .. _`Format Specification Mini-Language`: http://docs.python.org/library/string.html#format-specification-mini-language Result and Match Objects ------------------------ The result of a ``parse()`` and ``search()`` operation is either ``None`` (no match), a ``Result`` instance or a ``Match`` instance if ``evaluate_result`` is False. The ``Result`` instance has three attributes: ``fixed`` A tuple of the fixed-position, anonymous fields extracted from the input. ``named`` A dictionary of the named fields extracted from the input. ``spans`` A dictionary mapping the names and fixed position indices matched to a 2-tuple slice range of where the match occurred in the input. The span does not include any stripped padding (alignment or width). The ``Match`` instance has one method: ``evaluate_result()`` Generates and returns a ``Result`` instance for this ``Match`` object. Custom Type Conversions ----------------------- If you wish to have matched fields automatically converted to your own type you may pass in a dictionary of type conversion information to ``parse()`` and ``compile()``. The converter will be passed the field string matched. Whatever it returns will be substituted in the ``Result`` instance for that field. Your custom type conversions may override the builtin types if you supply one with the same identifier: .. code-block:: pycon >>> def shouty(string): ... return string.upper() ... >>> parse('{:shouty} world', 'hello world', dict(shouty=shouty)) If the type converter has the optional ``pattern`` attribute, it is used as regular expression for better pattern matching (instead of the default one): .. code-block:: pycon >>> def parse_number(text): ... return int(text) >>> parse_number.pattern = r'\d+' >>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) >>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number)) >>> assert _ is None, "MISMATCH" You can also use the ``with_pattern(pattern)`` decorator to add this information to a type converter function: .. code-block:: pycon >>> from parse import with_pattern >>> @with_pattern(r'\d+') ... def parse_number(text): ... return int(text) >>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) A more complete example of a custom type might be: .. code-block:: pycon >>> yesno_mapping = { ... "yes": True, "no": False, ... "on": True, "off": False, ... "true": True, "false": False, ... } >>> @with_pattern(r"|".join(yesno_mapping)) ... def parse_yesno(text): ... return yesno_mapping[text.lower()] If the type converter ``pattern`` uses regex-grouping (with parenthesis), you should indicate this by using the optional ``regex_group_count`` parameter in the ``with_pattern()`` decorator: .. code-block:: pycon >>> @with_pattern(r'((\d+))', regex_group_count=2) ... def parse_number2(text): ... return int(text) >>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2)) Otherwise, this may cause parsing problems with unnamed/fixed parameters. Potential Gotchas ----------------- ``parse()`` will always match the shortest text necessary (from left to right) to fulfil the parse pattern, so for example: .. code-block:: pycon >>> pattern = '{dir1}/{dir2}' >>> data = 'root/parent/subdir' >>> sorted(parse(pattern, data).named.items()) [('dir1', 'root'), ('dir2', 'parent/subdir')] So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit the pattern, the actual match represents the shortest successful match for ``dir1``. ---- - 1.18.0 Correct bug in int parsing introduced in 1.16.0 (thanks @maxxk) - 1.17.0 Make left- and center-aligned search consume up to next space - 1.16.0 Make compiled parse objects pickleable (thanks @martinResearch) - 1.15.0 Several fixes for parsing non-base 10 numbers (thanks @vladikcomper) - 1.14.0 More broad acceptance of Fortran number format (thanks @purpleskyfall) - 1.13.1 Project metadata correction. - 1.13.0 Handle Fortran formatted numbers with no leading 0 before decimal point (thanks @purpleskyfall). Handle comparison of FixedTzOffset with other types of object. - 1.12.1 Actually use the `case_sensitive` arg in compile (thanks @jacquev6) - 1.12.0 Do not assume closing brace when an opening one is found (thanks @mattsep) - 1.11.1 Revert having unicode char in docstring, it breaks Bamboo builds(?!) - 1.11.0 Implement `__contains__` for Result instances. - 1.10.0 Introduce a "letters" matcher, since "w" matches numbers also. - 1.9.1 Fix deprecation warnings around backslashes in regex strings (thanks Mickael Schoentgen). Also fix some documentation formatting issues. - 1.9.0 We now honor precision and width specifiers when parsing numbers and strings, allowing parsing of concatenated elements of fixed width (thanks Julia Signell) - 1.8.4 Add LICENSE file at request of packagers. Correct handling of AM/PM to follow most common interpretation. Correct parsing of hexadecimal that looks like a binary prefix. Add ability to parse case sensitively. Add parsing of numbers to Decimal with "F" (thanks John Vandenberg) - 1.8.3 Add regex_group_count to with_pattern() decorator to support user-defined types that contain brackets/parenthesis (thanks Jens Engel) - 1.8.2 add documentation for including braces in format string - 1.8.1 ensure bare hexadecimal digits are not matched - 1.8.0 support manual control over result evaluation (thanks Timo Furrer) - 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow more than 100 re groups in Python 3.5+ (thanks David King) - 1.6.6 parse Linux system log dates (thanks Alex Cowan) - 1.6.5 handle precision in float format (thanks Levi Kilcher) - 1.6.4 handle pipe "|" characters in parse string (thanks Martijn Pieters) - 1.6.3 handle repeated instances of named fields, fix bug in PM time overflow - 1.6.2 fix logging to use local, not root logger (thanks Necku) - 1.6.1 be more flexible regarding matched ISO datetimes and timezones in general, fix bug in timezones without ":" and improve docs - 1.6.0 add support for optional ``pattern`` attribute in user-defined types (thanks Jens Engel) - 1.5.3 fix handling of question marks - 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel) - 1.5.1 implement handling of named datetime fields - 1.5 add handling of dotted field names (thanks Sebastian Thiel) - 1.4.1 fix parsing of "0" in int conversion (thanks James Rowe) - 1.4 add __getitem__ convenience access on Result. - 1.3.3 fix Python 2.5 setup.py issue. - 1.3.2 fix Python 3.2 setup.py issue. - 1.3.1 fix a couple of Python 3.2 compatibility issues. - 1.3 added search() and findall(); removed compile() from ``import *`` export as it overwrites builtin. - 1.2 added ability for custom and override type conversions to be provided; some cleanup - 1.1.9 to keep things simpler number sign is handled automatically; significant robustification in the face of edge-case input. - 1.1.8 allow "d" fields to have number base "0x" etc. prefixes; fix up some field type interactions after stress-testing the parser; implement "%" type. - 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported). - 1.1.6 add "e" and "g" field types; removed redundant "h" and "X"; removed need for explicit "#". - 1.1.5 accept textual dates in more places; Result now holds match span positions. - 1.1.4 fixes to some int type conversion; implemented "=" alignment; added date/time parsing with a variety of formats handled. - 1.1.3 type conversion is automatic based on specified field types. Also added "f" and "n" types. - 1.1.2 refactored, added compile() and limited ``from parse import *`` - 1.1.1 documentation improvements - 1.1.0 implemented more of the `Format Specification Mini-Language`_ and removed the restriction on mixing fixed-position and named fields - 1.0.0 initial release This code is copyright 2012-2020 Richard Jones See the end of the source file for the license of use. ''' from __future__ import absolute_import __version__ = '1.18.0' # yes, I now have two problems import re import sys from datetime import datetime, time, tzinfo, timedelta from decimal import Decimal from functools import partial import logging __all__ = 'parse search findall with_pattern'.split() log = logging.getLogger(__name__) def with_pattern(pattern, regex_group_count=None): r"""Attach a regular expression pattern matcher to a custom type converter function. This annotates the type converter with the :attr:`pattern` attribute. EXAMPLE: >>> import parse >>> @parse.with_pattern(r"\d+") ... def parse_number(text): ... return int(text) is equivalent to: >>> def parse_number(text): ... return int(text) >>> parse_number.pattern = r"\d+" :param pattern: regular expression pattern (as text) :param regex_group_count: Indicates how many regex-groups are in pattern. :return: wrapped function """ def decorator(func): func.pattern = pattern func.regex_group_count = regex_group_count return func return decorator class int_convert: """Convert a string to an integer. The string may start with a sign. It may be of a base other than 2, 8, 10 or 16. If base isn't specified, it will be detected automatically based on a string format. When string starts with a base indicator, 0#nnnn, it overrides the default base of 10. It may also have other non-numeric characters that we can ignore. """ CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' def __init__(self, base=None): self.base = base def __call__(self, string, match): if string[0] == '-': sign = -1 number_start = 1 elif string[0] == '+': sign = 1 number_start = 1 else: sign = 1 number_start = 0 base = self.base # If base wasn't specified, detect it automatically if base is None: # Assume decimal number, unless different base is detected base = 10 # For number formats starting with 0b, 0o, 0x, use corresponding base ... if string[number_start] == '0' and len(string) - number_start > 2: if string[number_start + 1] in 'bB': base = 2 elif string[number_start + 1] in 'oO': base = 8 elif string[number_start + 1] in 'xX': base = 16 chars = int_convert.CHARS[: base] string = re.sub('[^%s]' % chars, '', string.lower()) return sign * int(string, base) class convert_first: """Convert the first element of a pair. This equivalent to lambda s,m: converter(s). But unlike a lambda function, it can be pickled """ def __init__(self, converter): self.converter = converter def __call__(self, string, match): return self.converter(string) def percentage(string, match): return float(string[:-1]) / 100.0 class FixedTzOffset(tzinfo): """Fixed offset in minutes east from UTC.""" ZERO = timedelta(0) def __init__(self, offset, name): self._offset = timedelta(minutes=offset) self._name = name def __repr__(self): return '<%s %s %s>' % (self.__class__.__name__, self._name, self._offset) def utcoffset(self, dt): return self._offset def tzname(self, dt): return self._name def dst(self, dt): return self.ZERO def __eq__(self, other): if not isinstance(other, FixedTzOffset): return False return self._name == other._name and self._offset == other._offset MONTHS_MAP = dict( Jan=1, January=1, Feb=2, February=2, Mar=3, March=3, Apr=4, April=4, May=5, Jun=6, June=6, Jul=7, July=7, Aug=8, August=8, Sep=9, September=9, Oct=10, October=10, Nov=11, November=11, Dec=12, December=12, ) DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' ALL_MONTHS_PAT = r'(%s)' % '|'.join(MONTHS_MAP) TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)' AM_PAT = r'(\s+[AP]M)' TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)' def date_convert( string, match, ymd=None, mdy=None, dmy=None, d_m_y=None, hms=None, am=None, tz=None, mm=None, dd=None, ): """Convert the incoming string containing some date / time info into a datetime instance. """ groups = match.groups() time_only = False if mm and dd: y = datetime.today().year m = groups[mm] d = groups[dd] elif ymd is not None: y, m, d = re.split(r'[-/\s]', groups[ymd]) elif mdy is not None: m, d, y = re.split(r'[-/\s]', groups[mdy]) elif dmy is not None: d, m, y = re.split(r'[-/\s]', groups[dmy]) elif d_m_y is not None: d, m, y = d_m_y d = groups[d] m = groups[m] y = groups[y] else: time_only = True H = M = S = u = 0 if hms is not None and groups[hms]: t = groups[hms].split(':') if len(t) == 2: H, M = t else: H, M, S = t if '.' in S: S, u = S.split('.') u = int(float('.' + u) * 1000000) S = int(S) H = int(H) M = int(M) if am is not None: am = groups[am] if am: am = am.strip() if am == 'AM' and H == 12: # correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock H -= 12 elif am == 'PM' and H == 12: # no correction needed: 12PM is midday, 12:00 by 24 hour clock pass elif am == 'PM': H += 12 if tz is not None: tz = groups[tz] if tz == 'Z': tz = FixedTzOffset(0, 'UTC') elif tz: tz = tz.strip() if tz.isupper(): # TODO use the awesome python TZ module? pass else: sign = tz[0] if ':' in tz: tzh, tzm = tz[1:].split(':') elif len(tz) == 4: # 'snnn' tzh, tzm = tz[1], tz[2:4] else: tzh, tzm = tz[1:3], tz[3:5] offset = int(tzm) + int(tzh) * 60 if sign == '-': offset = -offset tz = FixedTzOffset(offset, tz) if time_only: d = time(H, M, S, u, tzinfo=tz) else: y = int(y) if m.isdigit(): m = int(m) else: m = MONTHS_MAP[m] d = int(d) d = datetime(y, m, d, H, M, S, u, tzinfo=tz) return d class TooManyFields(ValueError): pass class RepeatedNameError(ValueError): pass # note: {} are handled separately # note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])') # allowed field types ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + ['t' + c for c in 'ieahgcts']) def extract_format(format, extra_types): """Pull apart the format [[fill]align][0][width][.precision][type]""" fill = align = None if format[0] in '<>=^': align = format[0] format = format[1:] elif len(format) > 1 and format[1] in '<>=^': fill = format[0] align = format[1] format = format[2:] zero = False if format and format[0] == '0': zero = True format = format[1:] width = '' while format: if not format[0].isdigit(): break width += format[0] format = format[1:] if format.startswith('.'): # Precision isn't needed but we need to capture it so that # the ValueError isn't raised. format = format[1:] # drop the '.' precision = '' while format: if not format[0].isdigit(): break precision += format[0] format = format[1:] # the rest is the type, if present type = format if type and type not in ALLOWED_TYPES and type not in extra_types: raise ValueError('format spec %r not recognised' % type) return locals() PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})""") class Parser(object): """Encapsulate a format string that may be used to parse other strings.""" def __init__(self, format, extra_types=None, case_sensitive=False): # a mapping of a name as in {hello.world} to a regex-group compatible # name, like hello__world Its used to prevent the transformation of # name-to-group and group to name to fail subtly, such as in: # hello_.world-> hello___world->hello._world self._group_to_name_map = {} # also store the original field name to group name mapping to allow # multiple instances of a name in the format string self._name_to_group_map = {} # and to sanity check the repeated instances store away the first # field type specification for the named field self._name_types = {} self._format = format if extra_types is None: extra_types = {} self._extra_types = extra_types if case_sensitive: self._re_flags = re.DOTALL else: self._re_flags = re.IGNORECASE | re.DOTALL self._fixed_fields = [] self._named_fields = [] self._group_index = 0 self._type_conversions = {} self._expression = self._generate_expression() self.__search_re = None self.__match_re = None log.debug('format %r -> %r', format, self._expression) def __repr__(self): if len(self._format) > 20: return '<%s %r>' % (self.__class__.__name__, self._format[:17] + '...') return '<%s %r>' % (self.__class__.__name__, self._format) @property def _search_re(self): if self.__search_re is None: try: self.__search_re = re.compile(self._expression, self._re_flags) except AssertionError: # access error through sys to keep py3k and backward compat e = str(sys.exc_info()[1]) if e.endswith('this version only supports 100 named groups'): raise TooManyFields( 'sorry, you are attempting to parse ' 'too many complex fields' ) return self.__search_re @property def _match_re(self): if self.__match_re is None: expression = r'^%s$' % self._expression try: self.__match_re = re.compile(expression, self._re_flags) except AssertionError: # access error through sys to keep py3k and backward compat e = str(sys.exc_info()[1]) if e.endswith('this version only supports 100 named groups'): raise TooManyFields( 'sorry, you are attempting to parse ' 'too many complex fields' ) except re.error: raise NotImplementedError( "Group names (e.g. (?P) can " "cause failure, as they are not escaped properly: '%s'" % expression ) return self.__match_re @property def named_fields(self): return self._named_fields.copy() @property def fixed_fields(self): return self._fixed_fields.copy() def parse(self, string, evaluate_result=True): """Match my format to the string exactly. Return a Result or Match instance or None if there's no match. """ m = self._match_re.match(string) if m is None: return None if evaluate_result: return self.evaluate_result(m) else: return Match(self, m) def search(self, string, pos=0, endpos=None, evaluate_result=True): """Search the string for my format. Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to search(string[:endpos]). If the ``evaluate_result`` argument is set to ``False`` a Match instance is returned instead of the actual Result instance. Return either a Result instance or None if there's no match. """ if endpos is None: endpos = len(string) m = self._search_re.search(string, pos, endpos) if m is None: return None if evaluate_result: return self.evaluate_result(m) else: return Match(self, m) def findall( self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True ): """Search "string" for all occurrences of "format". Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to search(string[:endpos]). Returns an iterator that holds Result or Match instances for each format match found. """ if endpos is None: endpos = len(string) return ResultIterator( self, string, pos, endpos, evaluate_result=evaluate_result ) def _expand_named_fields(self, named_fields): result = {} for field, value in named_fields.items(): # split 'aaa[bbb][ccc]...' into 'aaa' and '[bbb][ccc]...' basename, subkeys = re.match(r'([^\[]+)(.*)', field).groups() # create nested dictionaries {'aaa': {'bbb': {'ccc': ...}}} d = result k = basename if subkeys: for subkey in re.findall(r'\[[^\]]+\]', subkeys): d = d.setdefault(k, {}) k = subkey[1:-1] # assign the value to the last key d[k] = value return result def evaluate_result(self, m): '''Generate a Result instance for the given regex match object''' # ok, figure the fixed fields we've pulled out and type convert them fixed_fields = list(m.groups()) for n in self._fixed_fields: if n in self._type_conversions: fixed_fields[n] = self._type_conversions[n](fixed_fields[n], m) fixed_fields = tuple(fixed_fields[n] for n in self._fixed_fields) # grab the named fields, converting where requested groupdict = m.groupdict() named_fields = {} name_map = {} for k in self._named_fields: korig = self._group_to_name_map[k] name_map[korig] = k if k in self._type_conversions: value = self._type_conversions[k](groupdict[k], m) else: value = groupdict[k] named_fields[korig] = value # now figure the match spans spans = dict((n, m.span(name_map[n])) for n in named_fields) spans.update((i, m.span(n + 1)) for i, n in enumerate(self._fixed_fields)) # and that's our result return Result(fixed_fields, self._expand_named_fields(named_fields), spans) def _regex_replace(self, match): return '\\' + match.group(1) def _generate_expression(self): # turn my _format attribute into the _expression attribute e = [] for part in PARSE_RE.split(self._format): if not part: continue elif part == '{{': e.append(r'\{') elif part == '}}': e.append(r'\}') elif part[0] == '{' and part[-1] == '}': # this will be a braces-delimited field to handle e.append(self._handle_field(part)) else: # just some text to match e.append(REGEX_SAFETY.sub(self._regex_replace, part)) return ''.join(e) def _to_group_name(self, field): # return a version of field which can be used as capture group, even # though it might contain '.' group = field.replace('.', '_').replace('[', '_').replace(']', '_') # make sure we don't collide ("a.b" colliding with "a_b") n = 1 while group in self._group_to_name_map: n += 1 if '.' in field: group = field.replace('.', '_' * n) elif '_' in field: group = field.replace('_', '_' * n) else: raise KeyError('duplicated group name %r' % (field,)) # save off the mapping self._group_to_name_map[group] = field self._name_to_group_map[field] = group return group def _handle_field(self, field): # first: lose the braces field = field[1:-1] # now figure whether this is an anonymous or named field, and whether # there's any format specification format = '' if field and field[0].isalpha(): if ':' in field: name, format = field.split(':') else: name = field if name in self._name_to_group_map: if self._name_types[name] != format: raise RepeatedNameError( 'field type %r for field "%s" ' 'does not match previous seen type %r' % (format, name, self._name_types[name]) ) group = self._name_to_group_map[name] # match previously-seen value return r'(?P=%s)' % group else: group = self._to_group_name(name) self._name_types[name] = format self._named_fields.append(group) # this will become a group, which must not contain dots wrap = r'(?P<%s>%%s)' % group else: self._fixed_fields.append(self._group_index) wrap = r'(%s)' if ':' in field: format = field[1:] group = self._group_index # simplest case: no type specifier ({} or {name}) if not format: self._group_index += 1 return wrap % r'.+?' # decode the format specification format = extract_format(format, self._extra_types) # figure type conversions, if any type = format['type'] is_numeric = type and type in 'n%fegdobx' if type in self._extra_types: type_converter = self._extra_types[type] s = getattr(type_converter, 'pattern', r'.+?') regex_group_count = getattr(type_converter, 'regex_group_count', 0) if regex_group_count is None: regex_group_count = 0 self._group_index += regex_group_count self._type_conversions[group] = convert_first(type_converter) elif type == 'n': s = r'\d{1,3}([,.]\d{3})*' self._group_index += 1 self._type_conversions[group] = int_convert(10) elif type == 'b': s = r'(0[bB])?[01]+' self._type_conversions[group] = int_convert(2) self._group_index += 1 elif type == 'o': s = r'(0[oO])?[0-7]+' self._type_conversions[group] = int_convert(8) self._group_index += 1 elif type == 'x': s = r'(0[xX])?[0-9a-fA-F]+' self._type_conversions[group] = int_convert(16) self._group_index += 1 elif type == '%': s = r'\d+(\.\d+)?%' self._group_index += 1 self._type_conversions[group] = percentage elif type == 'f': s = r'\d*\.\d+' self._type_conversions[group] = convert_first(float) elif type == 'F': s = r'\d*\.\d+' self._type_conversions[group] = convert_first(Decimal) elif type == 'e': s = r'\d*\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' self._type_conversions[group] = convert_first(float) elif type == 'g': s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF' self._group_index += 2 self._type_conversions[group] = convert_first(float) elif type == 'd': if format.get('width'): width = r'{1,%s}' % int(format['width']) else: width = '+' s = r'\d{w}|[-+ ]?0[xX][0-9a-fA-F]{w}|[-+ ]?0[bB][01]{w}|[-+ ]?0[oO][0-7]{w}'.format( w=width ) self._type_conversions[ group ] = int_convert() # do not specify number base, determine it automatically elif type == 'ti': s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % TIME_PAT n = self._group_index self._type_conversions[group] = partial( date_convert, ymd=n + 1, hms=n + 4, tz=n + 7 ) self._group_index += 7 elif type == 'tg': s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % ( ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT, ) n = self._group_index self._type_conversions[group] = partial( date_convert, dmy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 ) self._group_index += 9 elif type == 'ta': s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % ( ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT, ) n = self._group_index self._type_conversions[group] = partial( date_convert, mdy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 ) self._group_index += 9 elif type == 'te': # this will allow microseconds through if they're present, but meh s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % ( DAYS_PAT, MONTHS_PAT, TIME_PAT, TZ_PAT, ) n = self._group_index self._type_conversions[group] = partial( date_convert, dmy=n + 3, hms=n + 5, tz=n + 8 ) self._group_index += 8 elif type == 'th': # slight flexibility here from the stock Apache format s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, TZ_PAT) n = self._group_index self._type_conversions[group] = partial( date_convert, dmy=n + 1, hms=n + 3, tz=n + 6 ) self._group_index += 6 elif type == 'tc': s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % ( DAYS_PAT, MONTHS_PAT, TIME_PAT, ) n = self._group_index self._type_conversions[group] = partial( date_convert, d_m_y=(n + 4, n + 3, n + 8), hms=n + 5 ) self._group_index += 8 elif type == 'tt': s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT) n = self._group_index self._type_conversions[group] = partial( date_convert, hms=n + 1, am=n + 4, tz=n + 5 ) self._group_index += 5 elif type == 'ts': s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT n = self._group_index self._type_conversions[group] = partial( date_convert, mm=n + 1, dd=n + 3, hms=n + 5 ) self._group_index += 5 elif type == 'l': s = r'[A-Za-z]+' elif type: s = r'\%s+' % type elif format.get('precision'): if format.get('width'): s = r'.{%s,%s}?' % (format['width'], format['precision']) else: s = r'.{1,%s}?' % format['precision'] elif format.get('width'): s = r'.{%s,}?' % format['width'] else: s = r'.+?' align = format['align'] fill = format['fill'] # handle some numeric-specific things like fill and sign if is_numeric: # prefix with something (align "=" trumps zero) if align == '=': # special case - align "=" acts like the zero above but with # configurable fill defaulting to "0" if not fill: fill = '0' s = r'%s*' % fill + s # allow numbers to be prefixed with a sign s = r'[-+ ]?' + s if not fill: fill = ' ' # Place into a group now - this captures the value we want to keep. # Everything else from now is just padding to be stripped off if wrap: s = wrap % s self._group_index += 1 if format['width']: # all we really care about is that if the format originally # specified a width then there will probably be padding - without # an explicit alignment that'll mean right alignment with spaces # padding if not align: align = '>' if fill in r'.\+?*[](){}^$': fill = '\\' + fill # align "=" has been handled if align == '<': s = '%s%s+' % (s, fill) elif align == '>': s = '%s*%s' % (fill, s) elif align == '^': s = '%s*%s%s+' % (fill, s, fill) return s class Result(object): """The result of a parse() or search(). Fixed results may be looked up using `result[index]`. Named results may be looked up using `result['name']`. Named results may be tested for existence using `'name' in result`. """ def __init__(self, fixed, named, spans): self.fixed = fixed self.named = named self.spans = spans def __getitem__(self, item): if isinstance(item, int): return self.fixed[item] return self.named[item] def __repr__(self): return '<%s %r %r>' % (self.__class__.__name__, self.fixed, self.named) def __contains__(self, name): return name in self.named class Match(object): """The result of a parse() or search() if no results are generated. This class is only used to expose internal used regex match objects to the user and use them for external Parser.evaluate_result calls. """ def __init__(self, parser, match): self.parser = parser self.match = match def evaluate_result(self): '''Generate results for this Match''' return self.parser.evaluate_result(self.match) class ResultIterator(object): """The result of a findall() operation. Each element is a Result instance. """ def __init__(self, parser, string, pos, endpos, evaluate_result=True): self.parser = parser self.string = string self.pos = pos self.endpos = endpos self.evaluate_result = evaluate_result def __iter__(self): return self def __next__(self): m = self.parser._search_re.search(self.string, self.pos, self.endpos) if m is None: raise StopIteration() self.pos = m.end() if self.evaluate_result: return self.parser.evaluate_result(m) else: return Match(self.parser, m) # pre-py3k compat next = __next__ def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False): """Using "format" attempt to pull values from "string". The format must match the string contents exactly. If the value you're looking for is instead just a part of the string use search(). If ``evaluate_result`` is True the return value will be an Result instance with two attributes: .fixed - tuple of fixed-position values from the string .named - dict of named values from the string If ``evaluate_result`` is False the return value will be a Match instance with one method: .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. If the format is invalid a ValueError will be raised. See the module documentation for the use of "extra_types". In the case there is no match parse() will return None. """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) return p.parse(string, evaluate_result=evaluate_result) def search( format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False, ): """Search "string" for the first occurrence of "format". The format may occur anywhere within the string. If instead you wish for the format to exactly match the string use parse(). Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to search(string[:endpos]). If ``evaluate_result`` is True the return value will be an Result instance with two attributes: .fixed - tuple of fixed-position values from the string .named - dict of named values from the string If ``evaluate_result`` is False the return value will be a Match instance with one method: .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. If the format is invalid a ValueError will be raised. See the module documentation for the use of "extra_types". In the case there is no match parse() will return None. """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) return p.search(string, pos, endpos, evaluate_result=evaluate_result) def findall( format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False, ): """Search "string" for all occurrences of "format". You will be returned an iterator that holds Result instances for each format match found. Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to search(string[:endpos]). If ``evaluate_result`` is True each returned Result instance has two attributes: .fixed - tuple of fixed-position values from the string .named - dict of named values from the string If ``evaluate_result`` is False each returned value is a Match instance with one method: .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. If the format is invalid a ValueError will be raised. See the module documentation for the use of "extra_types". """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) return p.findall(string, pos, endpos, evaluate_result=evaluate_result) def compile(format, extra_types=None, case_sensitive=False): """Create a Parser instance to parse "format". The resultant Parser has a method .parse(string) which behaves in the same manner as parse(format, string). The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. Use this function if you intend to parse many strings with the same format. See the module documentation for the use of "extra_types". Returns a Parser instance. """ return Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) # Copyright (c) 2012-2020 Richard Jones # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # vim: set filetype=python ts=4 sw=4 et si tw=75 parse_type-0.5.6/parse_type/parse_util.py000066400000000000000000000140401372661661400206040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # pylint: disable=missing-docstring """ Provides generic utility classes for the :class:`parse.Parser` class. """ from __future__ import absolute_import from collections import namedtuple import parse import six # -- HELPER-CLASS: For format part in a Field. # REQUIRES: Python 2.6 or newer. # pylint: disable=redefined-builtin, too-many-arguments FormatSpec = namedtuple("FormatSpec", ["type", "width", "zero", "align", "fill", "precision"]) def make_format_spec(type=None, width="", zero=False, align=None, fill=None, precision=None): return FormatSpec(type, width, zero, align, fill, precision) # pylint: enable=redefined-builtin class Field(object): """ Provides a ValueObject for a Field in a parse expression. Examples: * "{}" * "{name}" * "{:format}" * "{name:format}" Format specification: [[fill]align][0][width][.precision][type] """ # pylint: disable=redefined-builtin ALIGN_CHARS = '<>=^' def __init__(self, name="", format=None): self.name = name self.format = format self._format_spec = None def set_format(self, format): self.format = format self._format_spec = None @property def has_format(self): return bool(self.format) @property def format_spec(self): if not self._format_spec and self.format: self._format_spec = self.extract_format_spec(self.format) return self._format_spec def __str__(self): name = self.name or "" if self.has_format: return "{%s:%s}" % (name, self.format) return "{%s}" % name def __eq__(self, other): if isinstance(other, Field): format1 = self.format or "" format2 = other.format or "" return (self.name == other.name) and (format1 == format2) elif isinstance(other, six.string_types): return str(self) == other else: raise ValueError(other) def __ne__(self, other): return not self.__eq__(other) @staticmethod def make_format(format_spec): """Build format string from a format specification. :param format_spec: Format specification (as FormatSpec object). :return: Composed format (as string). """ fill = '' align = '' zero = '' width = format_spec.width if format_spec.align: align = format_spec.align[0] if format_spec.fill: fill = format_spec.fill[0] if format_spec.zero: zero = '0' precision_part = "" if format_spec.precision: precision_part = ".%s" % format_spec.precision # -- FORMAT-SPEC: [[fill]align][0][width][.precision][type] return "%s%s%s%s%s%s" % (fill, align, zero, width, precision_part, format_spec.type) @classmethod def extract_format_spec(cls, format): """Pull apart the format: [[fill]align][0][width][.precision][type]""" # -- BASED-ON: parse.extract_format() # pylint: disable=redefined-builtin, unsubscriptable-object if not format: raise ValueError("INVALID-FORMAT: %s (empty-string)" % format) orig_format = format fill = align = None if format[0] in cls.ALIGN_CHARS: align = format[0] format = format[1:] elif len(format) > 1 and format[1] in cls.ALIGN_CHARS: fill = format[0] align = format[1] format = format[2:] zero = False if format and format[0] == '0': zero = True format = format[1:] width = '' while format: if not format[0].isdigit(): break width += format[0] format = format[1:] precision = None if format.startswith('.'): # Precision isn't needed but we need to capture it so that # the ValueError isn't raised. format = format[1:] # drop the '.' precision = '' while format: if not format[0].isdigit(): break precision += format[0] format = format[1:] # the rest is the type, if present type = format if not type: raise ValueError("INVALID-FORMAT: %s (without type)" % orig_format) return FormatSpec(type, width, zero, align, fill, precision) class FieldParser(object): """ Utility class that parses/extracts fields in parse expressions. """ @classmethod def parse(cls, text): if not (text.startswith('{') and text.endswith('}')): message = "FIELD-SCHEMA MISMATCH: text='%s' (missing braces)" % text raise ValueError(message) # first: lose the braces text = text[1:-1] if ':' in text: # -- CASE: Typed field with format. name, format_ = text.split(':') else: name = text format_ = None return Field(name, format_) @classmethod def extract_fields(cls, schema): """Extract fields in a parse expression schema. :param schema: Parse expression schema/format to use (as string). :return: Generator for fields in schema (as Field objects). """ # -- BASED-ON: parse.Parser._generate_expression() for part in parse.PARSE_RE.split(schema): if not part or part == '{{' or part == '}}': continue elif part[0] == '{': # this will be a braces-delimited field to handle yield cls.parse(part) @classmethod def extract_types(cls, schema): """Extract types (names) for typed fields (with format/type part). :param schema: Parser schema/format to use. :return: Generator for type names (as string). """ for field in cls.extract_fields(schema): if field.has_format: yield field.format_spec.type parse_type-0.5.6/py.requirements/000077500000000000000000000000001372661661400170635ustar00rootroot00000000000000parse_type-0.5.6/py.requirements/all.txt000066400000000000000000000007021372661661400203730ustar00rootroot00000000000000# ============================================================================ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: All requirements # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ -r basic.txt -r develop.txt -r testing.txt -r py26_more.txt parse_type-0.5.6/py.requirements/basic.txt000066400000000000000000000006651372661661400207140ustar00rootroot00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS: Normal usage/installation (minimal) # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ parse >= 1.8.4 enum34 six >= 1.11.0 parse_type-0.5.6/py.requirements/ci.travis.txt000066400000000000000000000006551372661661400215340ustar00rootroot00000000000000pytest < 5.0; python_version < '3.0' pytest >= 5.0; python_version >= '3.0' pytest-html >= 1.19.0 unittest2; python_version < '2.7' ordereddict; python_version < '2.7' # -- NEEDED: By some tests (as proof of concept) # NOTE: path.py-10.1 is required for python2.6 # HINT: path.py => path (python-install-package was renamed for python3) path.py >= 11.5.0; python_version < '3.5' path >= 13.1.0; python_version >= '3.5' parse_type-0.5.6/py.requirements/develop.txt000066400000000000000000000014451372661661400212660ustar00rootroot00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: parse_type -- For development only # ============================================================================ # -- DEVELOPMENT SUPPORT: invoke >= 1.2.0 six >= 1.11.0 pathlib; python_version <= '3.4' # -- HINT: path.py => path (python-install-package was renamed for python3) path.py >= 11.5.0; python_version < '3.5' path >= 13.1.0; python_version >= '3.5' # For cleanup of python files: py.cleanup pycmd # -- PROJECT ADMIN SUPPORT: # OLD: bumpversion bump2version >= 0.5.6 # -- RELEASE MANAGEMENT: Push package to pypi. twine >= 1.13.0 # -- PYTHON2/PYTHON3 COMPATIBILITY: modernize >= 0.5 pylint # -- RELATED: -r testing.txt -r docs.txt # -- DISABLED: # -r optional.txt parse_type-0.5.6/py.requirements/docs.txt000066400000000000000000000004131372661661400205520ustar00rootroot00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS: For documentation generation # ============================================================================ # sphinxcontrib-cheeseshop >= 0.2 Sphinx >= 1.5 parse_type-0.5.6/py.requirements/optional.txt000066400000000000000000000005751372661661400214600ustar00rootroot00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: parse_type -- Optional for development # ============================================================================ # -- GIT MULTI-REPO TOOL: wstool # REQUIRES: wstool >= 0.1.18 (which is not in pypi.org, yet) https://github.com/vcstools/wstool/archive/0.1.18.zip parse_type-0.5.6/py.requirements/py26_more.txt000066400000000000000000000000461372661661400214460ustar00rootroot00000000000000ordereddict; python_version <= '2.6' parse_type-0.5.6/py.requirements/testing.txt000066400000000000000000000006371372661661400213070ustar00rootroot00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: parse_type -- For testing only # ============================================================================ pytest >= 4.2 pytest-html >= 1.16 pytest-cov pytest-runner # -- PYTHON 2.6 SUPPORT: unittest2; python_version <= '2.6' tox >= 2.8 coverage >= 4.4 # -- NEEDED-FOR: toxcmd.py argparse parse_type-0.5.6/pytest.ini000066400000000000000000000022001372661661400157340ustar00rootroot00000000000000# ============================================================================ # PYTEST CONFIGURATION FILE: pytest.ini # ============================================================================ # SEE ALSO: # * http://pytest.org/ # * http://pytest.org/latest/customize.html # * http://pytest.org/latest/usage.html # * http://pytest.org/latest/example/pythoncollection.html#change-naming-conventions # ============================================================================ # MORE OPTIONS: # addopts = # python_classes=*Test # python_functions=test # ============================================================================ [pytest] minversion = 4.2 testpaths = tests python_files = test_*.py junit_family = xunit2 addopts = --metadata PACKAGE_UNDER_TEST parse_type --metadata PACKAGE_VERSION 0.5.6 --html=build/testing/report.html --self-contained-html --junit-xml=build/testing/report.xml # markers = # smoke # slow # -- PREPARED: # filterwarnings = # ignore:.*invalid escape sequence.*:DeprecationWarning # -- BACKWARD COMPATIBILITY: pytest < 2.8 norecursedirs = .git .tox build dist .venv* tmp* _* parse_type-0.5.6/setup.cfg000066400000000000000000000006271372661661400155370ustar00rootroot00000000000000# -- CONVENIENCE: Use pytest-runner (ptr) as test runner. [aliases] docs = build_sphinx test = pytest [build_sphinx] source-dir = docs/ build-dir = build/docs builder = html all_files = true [easy_install] # set the default location to install packages # install_dir = eggs # find_links = https://github.com/jenisys/parse_type [upload_docs] upload-dir = build/docs/html [bdist_wheel] universal = 1 parse_type-0.5.6/setup.py000066400000000000000000000106221372661661400154240ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Setup script for "parse_type" package. USAGE: python setup.py install # OR: pip install . SEE ALSO: * https://pypi.org/pypi/parse_type * https://github.com/jenisys/parse_type RELATED: * https://setuptools.readthedocs.io/en/latest/history.html """ import sys import os.path sys.path.insert(0, os.curdir) # -- USE: setuptools from setuptools import setup, find_packages # ----------------------------------------------------------------------------- # PREPARE SETUP: # ----------------------------------------------------------------------------- HERE = os.path.dirname(__file__) python_version = float('%s.%s' % sys.version_info[:2]) README = os.path.join(HERE, "README.rst") long_description = ''.join(open(README).readlines()[4:]) extra = dict( tests_require=[ "pytest < 5.0; python_version < '3.0'", # >= 4.2 "pytest >= 5.0; python_version >= '3.0'", "pytest-html >= 1.19.0", # -- PYTHON 2.6 SUPPORT: "unittest2; python_version < '2.7'", ], ) if python_version >= 3.0: extra["use_2to3"] = True # -- NICE-TO-HAVE: # # FILE: setup.cfg -- Use pytest-runner (ptr) as test runner. # [aliases] # test = ptr # USE_PYTEST_RUNNER = os.environ.get("PYSETUP_TEST", "pytest") == "pytest" USE_PYTEST_RUNNER = os.environ.get("PYSETUP_TEST", "no") == "pytest" if USE_PYTEST_RUNNER: extra["tests_require"].append("pytest-runner") # ----------------------------------------------------------------------------- # UTILITY: # ----------------------------------------------------------------------------- def find_packages_by_root_package(where): """ Better than excluding everything that is not needed, collect only what is needed. """ root_package = os.path.basename(where) packages = [ "%s.%s" % (root_package, sub_package) for sub_package in find_packages(where)] packages.insert(0, root_package) return packages # ----------------------------------------------------------------------------- # SETUP: # ----------------------------------------------------------------------------- setup( name = "parse_type", version = "0.5.6", author = "Jens Engel", author_email = "jenisys@noreply.github.com", url = "https://github.com/jenisys/parse_type", download_url= "http://pypi.python.org/pypi/parse_type", description = "Simplifies to build parse types based on the parse module", long_description = long_description, keywords= "parse, parsing", license = "BSD", packages = find_packages_by_root_package("parse_type"), include_package_data = True, # -- REQUIREMENTS: python_requires=">=2.7, !=3.0.*, !=3.1.*", install_requires=[ "parse >= 1.18.0; python_version >= '3.0'", "parse >= 1.13.1; python_version <= '2.7'", # -- MAYBE, related to issue #15: # "parse == 1.13.1; python_version <= '2.7'", "enum34; python_version < '3.4'", "six >= 1.11", "ordereddict; python_version < '2.7'", ], extras_require={ 'docs': ["sphinx>=1.2"], 'develop': [ "coverage >= 4.4", "pytest < 5.0; python_version < '3.0'", # >= 4.2 "pytest >= 5.0; python_version >= '3.0'", "pytest-html >= 1.19.0", "pytest-cov", "tox >= 2.8", ], }, test_suite = "tests", test_loader = "setuptools.command.test:ScanningLoader", zip_safe = True, classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Web Environment", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: BSD License", ], platforms = ['any'], **extra ) parse_type-0.5.6/tasks/000077500000000000000000000000001372661661400150365ustar00rootroot00000000000000parse_type-0.5.6/tasks/__init__.py000066400000000000000000000043661372661661400171600ustar00rootroot00000000000000# -*- coding: UTF-8 -*- # pylint: disable=wrong-import-position, wrong-import-order """ Invoke build script. Show all tasks with:: invoke -l .. seealso:: * http://pyinvoke.org * https://github.com/pyinvoke/invoke """ from __future__ import absolute_import # ----------------------------------------------------------------------------- # BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed # ----------------------------------------------------------------------------- from . import _setup # pylint: disable=wrong-import-order import os.path import sys INVOKE_MINVERSION = "1.2.0" _setup.setup_path() _setup.require_invoke_minversion(INVOKE_MINVERSION) # ----------------------------------------------------------------------------- # IMPORTS: # ----------------------------------------------------------------------------- import sys from invoke import Collection # -- TASK-LIBRARY: from . import _tasklet_cleanup as cleanup from . import test from . import release # DISABLED: from . import docs # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- # None # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- namespace = Collection() namespace.add_collection(Collection.from_module(cleanup), name="cleanup") namespace.add_collection(Collection.from_module(test)) namespace.add_collection(Collection.from_module(release)) # -- DISABLED: namespace.add_collection(Collection.from_module(docs)) namespace.configure({ "tasks": { "auto_dash_names": False } }) # -- ENSURE: python cleanup is used for this project. cleanup.cleanup_tasks.add_task(cleanup.clean_python) # -- INJECT: clean configuration into this namespace namespace.configure(cleanup.namespace.configuration()) if sys.platform.startswith("win"): # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows) from ._compat_shutil import which run_settings = dict(echo=True, pty=False, shell=which("cmd")) namespace.configure({"run": run_settings}) else: namespace.configure({"run": dict(echo=True, pty=True)}) parse_type-0.5.6/tasks/__main__.py000066400000000000000000000035521372661661400171350ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Provides "invoke" script when invoke is not installed. Note that this approach uses the "tasks/_vendor/invoke.zip" bundle package. Usage:: # -- INSTEAD OF: invoke command # Show invoke version python -m tasks --version # List all tasks python -m tasks -l .. seealso:: * http://pyinvoke.org * https://github.com/pyinvoke/invoke Examples for Invoke Scripts using the Bundle ------------------------------------------------------------------------------- For UNIX like platforms: .. code-block:: sh #!/bin/sh #!/bin/bash # RUN INVOKE: From bundled ZIP file (with Bourne shell/bash script). # FILE: invoke.sh (in directory that contains tasks/ directory) HERE=$(dirname $0) export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" python ${HERE}/tasks/_vendor/invoke.zip $* For Windows platform: .. code-block:: bat @echo off REM RUN INVOKE: From bundled ZIP file (with Windows Batchfile). REM FILE: invoke.cmd (in directory that contains tasks/ directory) setlocal set HERE=%~dp0 set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" if not defined PYTHON set PYTHON=python %PYTHON% %HERE%tasks/_vendor/invoke.zip "%*" """ from __future__ import absolute_import import os import sys # ----------------------------------------------------------------------------- # BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed # ----------------------------------------------------------------------------- # NOTE: tasks/__init__.py performs sys.path setup. os.environ["INVOKE_TASKS_USE_VENDOR_BUNDLES"] = "yes" # ----------------------------------------------------------------------------- # AUTO-MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": from invoke.main import program sys.exit(program.run()) parse_type-0.5.6/tasks/_compat_shutil.py000066400000000000000000000003341372661661400204220ustar00rootroot00000000000000# -*- coding: UTF-8 -*- # pylint: disable=unused-import # PYTHON VERSION COMPATIBILITY HELPER try: from shutil import which # -- SINCE: Python 3.3 except ImportError: from backports.shutil_which import which parse_type-0.5.6/tasks/_dry_run.py000066400000000000000000000022051372661661400172300ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Basic support to use a --dry-run mode w/ invoke tasks. .. code-block:: from ._dry_run import DryRunContext @task def destroy_something(ctx, path, dry_run=False): if dry_run: ctx = DryRunContext(ctx) # -- DRY-RUN MODE: Only echos commands. ctx.run("rm -rf {}".format(path)) """ from __future__ import print_function class DryRunContext(object): PREFIX = "DRY-RUN: " SCHEMA = "{prefix}{command}" SCHEMA_WITH_KWARGS = "{prefix}{command} (with kwargs={kwargs})" def __init__(self, ctx=None, prefix=None, schema=None): if prefix is None: prefix = self.PREFIX if schema is None: schema = self.SCHEMA self.ctx = ctx self.prefix = prefix self.schema = schema def run(self, command, **kwargs): message = self.schema.format(command=command, prefix=self.prefix, kwargs=kwargs) print(message) def sudo(self, command, **kwargs): command2 = "sudo %s" % command self.run(command2, **kwargs) parse_type-0.5.6/tasks/_setup.py000066400000000000000000000112471372661661400167140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Decides if vendor bundles are used or not. Setup python path accordingly. """ from __future__ import absolute_import, print_function import os.path import sys # ----------------------------------------------------------------------------- # DEFINES: # ----------------------------------------------------------------------------- HERE = os.path.dirname(__file__) TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor") INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip") INVOKE_BUNDLE_VERSION = "1.2.0" DEBUG_SYSPATH = False # ----------------------------------------------------------------------------- # EXCEPTIONS: # ----------------------------------------------------------------------------- class VersionRequirementError(SystemExit): pass # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def setup_path(invoke_minversion=None): """Setup python search and add ``TASKS_VENDOR_DIR`` (if available).""" # print("INVOKE.tasks: setup_path") if not os.path.isdir(TASKS_VENDOR_DIR): print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR) return elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path: # -- SETUP ALREADY DONE: # return pass use_vendor_bundles = os.environ.get("INVOKE_TASKS_USE_VENDOR_BUNDLES", "no") if need_vendor_bundles(invoke_minversion): use_vendor_bundles = "yes" if use_vendor_bundles == "yes": syspath_insert(0, os.path.abspath(TASKS_VENDOR_DIR)) if setup_path_for_bundle(INVOKE_BUNDLE, pos=1): import invoke bundle_path = os.path.relpath(INVOKE_BUNDLE, os.getcwd()) print("USING: %s (version: %s)" % (bundle_path, invoke.__version__)) else: # -- BEST-EFFORT: May rescue something syspath_append(os.path.abspath(TASKS_VENDOR_DIR)) setup_path_for_bundle(INVOKE_BUNDLE, pos=len(sys.path)) if DEBUG_SYSPATH: for index, p in enumerate(sys.path): print(" %d. %s" % (index, p)) def require_invoke_minversion(min_version, verbose=False): """Ensures that :mod:`invoke` has at the least the :param:`min_version`. Otherwise, :param min_version: Minimal acceptable invoke version (as string). :param verbose: Indicates if invoke.version should be shown. :raises: VersionRequirementError=SystemExit if requirement fails. """ # -- REQUIRES: sys.path is setup and contains invoke try: import invoke invoke_version = invoke.__version__ except ImportError: invoke_version = "__NOT_INSTALLED" if invoke_version < min_version: message = "REQUIRE: invoke.version >= %s (but was: %s)" % \ (min_version, invoke_version) message += "\nUSE: pip install invoke>=%s" % min_version raise VersionRequirementError(message) # pylint: disable=invalid-name INVOKE_VERSION = os.environ.get("INVOKE_VERSION", None) if verbose and not INVOKE_VERSION: os.environ["INVOKE_VERSION"] = invoke_version print("USING: invoke.version=%s" % invoke_version) def need_vendor_bundles(invoke_minversion=None): invoke_minversion = invoke_minversion or "0.0.0" need_vendor_answers = [] need_vendor_answers.append(need_vendor_bundle_invoke(invoke_minversion)) # -- REQUIRE: path.py try: import path need_bundle = False except ImportError: need_bundle = True need_vendor_answers.append(need_bundle) # -- DIAG: print("INVOKE: need_bundle=%s" % need_bundle1) # return need_bundle1 or need_bundle2 return any(need_vendor_answers) def need_vendor_bundle_invoke(invoke_minversion="0.0.0"): # -- REQUIRE: invoke try: import invoke need_bundle = invoke.__version__ < invoke_minversion if need_bundle: del sys.modules["invoke"] del invoke except ImportError: need_bundle = True except Exception: # pylint: disable=broad-except need_bundle = True return need_bundle # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def setup_path_for_bundle(bundle_path, pos=0): if os.path.exists(bundle_path): syspath_insert(pos, os.path.abspath(bundle_path)) return True return False def syspath_insert(pos, path): if path in sys.path: sys.path.remove(path) sys.path.insert(pos, path) def syspath_append(path): if path in sys.path: sys.path.remove(path) sys.path.append(path) parse_type-0.5.6/tasks/_tasklet_cleanup.py000066400000000000000000000252201372661661400207260ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Provides cleanup tasks for invoke build scripts (as generic invoke tasklet). Simplifies writing common, composable and extendable cleanup tasks. PYTHON PACKAGE REQUIREMENTS: * path.py >= 8.2.1 (as path-object abstraction) * pathlib (for ant-like wildcard patterns; since: python > 3.5) * pycmd (required-by: clean_python()) clean task: Add Additional Directories and Files to be removed ------------------------------------------------------------------------------- Create an invoke configuration file (YAML of JSON) with the additional configuration data: .. code-block:: yaml # -- FILE: invoke.yaml # USE: clean.directories, clean.files to override current configuration. clean: extra_directories: - **/tmp/ extra_files: - **/*.log - **/*.bak Registration of Cleanup Tasks ------------------------------ Other task modules often have an own cleanup task to recover the clean state. The :meth:`clean` task, that is provided here, supports the registration of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed, all registered cleanup tasks will be executed. EXAMPLE:: # -- FILE: tasks/docs.py from __future__ import absolute_import from invoke import task, Collection from tasklet_cleanup import cleanup_tasks, cleanup_dirs @task def clean(ctx, dry_run=False): "Cleanup generated documentation artifacts." cleanup_dirs(["build/docs"]) namespace = Collection(clean) ... # -- REGISTER CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_docs") cleanup_tasks.configure(namespace.configuration()) """ from __future__ import absolute_import, print_function import os.path import sys import pathlib from invoke import task, Collection from invoke.executor import Executor from invoke.exceptions import Exit, Failure, UnexpectedExit from path import Path # ----------------------------------------------------------------------------- # CLEANUP UTILITIES: # ----------------------------------------------------------------------------- def cleanup_accept_old_config(ctx): ctx.cleanup.directories.extend(ctx.clean.directories or []) ctx.cleanup.extra_directories.extend(ctx.clean.extra_directories or []) ctx.cleanup.files.extend(ctx.clean.files or []) ctx.cleanup.extra_files.extend(ctx.clean.extra_files or []) ctx.cleanup_all.directories.extend(ctx.clean_all.directories or []) ctx.cleanup_all.extra_directories.extend(ctx.clean_all.extra_directories or []) ctx.cleanup_all.files.extend(ctx.clean_all.files or []) ctx.cleanup_all.extra_files.extend(ctx.clean_all.extra_files or []) def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False): """Execute several cleanup tasks as part of the cleanup. REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks. :param ctx: Context object for the tasks. :param cleanup_tasks: Collection of cleanup tasks (as Collection). :param dry_run: Indicates dry-run mode (bool) """ # pylint: disable=redefined-outer-name executor = Executor(cleanup_tasks, ctx.config) failure_count = 0 for cleanup_task in cleanup_tasks.tasks: try: print("CLEANUP TASK: %s" % cleanup_task) executor.execute((cleanup_task, dict(dry_run=dry_run))) except (Exit, Failure, UnexpectedExit) as e: print("FAILURE in CLEANUP TASK: %s (GRACEFULLY-IGNORED)" % cleanup_task) failure_count += 1 if failure_count: print("CLEANUP TASKS: %d failure(s) occured" % failure_count) def cleanup_dirs(patterns, dry_run=False, workdir="."): """Remove directories (and their contents) recursively. Skips removal if directories does not exist. :param patterns: Directory name patterns, like "**/tmp*" (as list). :param dry_run: Dry-run mode indicator (as bool). :param workdir: Current work directory (default=".") """ current_dir = Path(workdir) python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath() warn2_counter = 0 for dir_pattern in patterns: for directory in path_glob(dir_pattern, current_dir): directory2 = directory.abspath() if sys.executable.startswith(directory2): # pylint: disable=line-too-long print("SKIP-SUICIDE: '%s' contains current python executable" % directory) continue elif directory2.startswith(python_basedir): # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT: if warn2_counter <= 4: print("SKIP-SUICIDE: '%s'" % directory) warn2_counter += 1 continue if not directory.isdir(): print("RMTREE: %s (SKIPPED: Not a directory)" % directory) continue if dry_run: print("RMTREE: %s (dry-run)" % directory) else: print("RMTREE: %s" % directory) directory.rmtree_p() def cleanup_files(patterns, dry_run=False, workdir="."): """Remove files or files selected by file patterns. Skips removal if file does not exist. :param patterns: File patterns, like "**/*.pyc" (as list). :param dry_run: Dry-run mode indicator (as bool). :param workdir: Current work directory (default=".") """ current_dir = Path(workdir) python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath() error_message = None error_count = 0 for file_pattern in patterns: for file_ in path_glob(file_pattern, current_dir): if file_.abspath().startswith(python_basedir): # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT: continue if not file_.isfile(): print("REMOVE: %s (SKIPPED: Not a file)" % file_) continue if dry_run: print("REMOVE: %s (dry-run)" % file_) else: print("REMOVE: %s" % file_) try: file_.remove_p() except os.error as e: message = "%s: %s" % (e.__class__.__name__, e) print(message + " basedir: "+ python_basedir) error_count += 1 if not error_message: error_message = message if False and error_message: class CleanupError(RuntimeError): pass raise CleanupError(error_message) def path_glob(pattern, current_dir=None): """Use pathlib for ant-like patterns, like: "**/*.py" :param pattern: File/directory pattern to use (as string). :param current_dir: Current working directory (as Path, pathlib.Path, str) :return Resolved Path (as path.Path). """ if not current_dir: current_dir = pathlib.Path.cwd() elif not isinstance(current_dir, pathlib.Path): # -- CASE: string, path.Path (string-like) current_dir = pathlib.Path(str(current_dir)) for p in current_dir.glob(pattern): yield Path(str(p)) # ----------------------------------------------------------------------------- # GENERIC CLEANUP TASKS: # ----------------------------------------------------------------------------- @task def clean(ctx, dry_run=False): """Cleanup temporary dirs/files to regain a clean state.""" cleanup_accept_old_config(ctx) directories = ctx.cleanup.directories or [] directories.extend(ctx.cleanup.extra_directories or []) files = ctx.cleanup.files or [] files.extend(ctx.cleanup.extra_files or []) # -- PERFORM CLEANUP: execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run) cleanup_dirs(directories, dry_run=dry_run) cleanup_files(files, dry_run=dry_run) @task(name="all", aliases=("distclean",)) def clean_all(ctx, dry_run=False): """Clean up everything, even the precious stuff. NOTE: clean task is executed first. """ cleanup_accept_old_config(ctx) directories = ctx.config.cleanup_all.directories or [] directories.extend(ctx.config.cleanup_all.extra_directories or []) files = ctx.config.cleanup_all.files or [] files.extend(ctx.config.cleanup_all.extra_files or []) # -- PERFORM CLEANUP: # HINT: Remove now directories, files first before cleanup-tasks. cleanup_dirs(directories, dry_run=dry_run) cleanup_files(files, dry_run=dry_run) execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run) clean(ctx, dry_run=dry_run) @task(name="python") def clean_python(ctx, dry_run=False): """Cleanup python related files/dirs: *.pyc, *.pyo, ...""" # MAYBE NOT: "**/__pycache__" cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"], dry_run=dry_run) if not dry_run: ctx.run("py.cleanup") cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run) # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- CLEANUP_EMPTY_CONFIG = { "directories": [], "files": [], "extra_directories": [], "extra_files": [], } def make_cleanup_config(**kwargs): config_data = CLEANUP_EMPTY_CONFIG.copy() config_data.update(kwargs) return config_data namespace = Collection(clean_all, clean_python) namespace.add_task(clean, default=True) namespace.configure({ "cleanup": make_cleanup_config( files=["*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~"] ), "cleanup_all": make_cleanup_config( directories=[".venv*", ".tox", "downloads", "tmp"] ), # -- BACKWARD-COMPATIBLE: OLD-STYLE "clean": CLEANUP_EMPTY_CONFIG.copy(), "clean_all": CLEANUP_EMPTY_CONFIG.copy(), }) # -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task) # NOTE: Can be used by other tasklets to register cleanup tasks. cleanup_tasks = Collection("cleanup_tasks") cleanup_all_tasks = Collection("cleanup_all_tasks") # -- EXTEND NORMAL CLEANUP-TASKS: # DISABLED: cleanup_tasks.add_task(clean_python) # # ----------------------------------------------------------------------------- # EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules # ----------------------------------------------------------------------------- def config_add_cleanup_dirs(directories): # pylint: disable=protected-access the_cleanup_directories = namespace._configuration["clean"]["directories"] the_cleanup_directories.extend(directories) def config_add_cleanup_files(files): # pylint: disable=protected-access the_cleanup_files = namespace._configuration["clean"]["files"] the_cleanup_files.extend(files) parse_type-0.5.6/tasks/_vendor/000077500000000000000000000000001372661661400164725ustar00rootroot00000000000000parse_type-0.5.6/tasks/_vendor/README.rst000066400000000000000000000021351372661661400201620ustar00rootroot00000000000000tasks/_vendor: Bundled vendor parts -- needed by tasks =============================================================================== This directory contains bundled archives that may be needed to run the tasks. Especially, it contains an executable "invoke.zip" archive. This archive can be used when invoke is not installed. To execute invoke from the bundled ZIP archive:: python -m tasks/_vendor/invoke.zip --help python -m tasks/_vendor/invoke.zip --version Example for a local "bin/invoke" script in a UNIX like platform environment:: #!/bin/bash # RUN INVOKE: From bundled ZIP file. HERE=$(dirname $0) python ${HERE}/../tasks/_vendor/invoke.zip $* Example for a local "bin/invoke.cmd" script in a Windows environment:: @echo off REM ========================================================================== REM RUN INVOKE: From bundled ZIP file. REM ========================================================================== setlocal set HERE=%~dp0 if not defined PYTHON set PYTHON=python %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*" parse_type-0.5.6/tasks/_vendor/invoke.zip000066400000000000000000005203711372661661400205210ustar00rootroot00000000000000PK u‹Iinvoke/UT =WMX^XMXux öPKnt‹I´’bpinvoke/__init__.pyUT  VMXØWMXux öuSÛnÛ0 }ÏWÝÃÒÂðô-(R @7]º=ÚŠLÇZlÉÓ%—=ìÛGÚ²ãm¤•txxxH–Ö4fG´N ªiõ ™Ò¥É²dr“|mþˆEÙÅJS×(ý$úéz3‡êRí¯°î4Gx<û „ |3ò3N+÷®¥p^ìj’ljáKc›H;¡xC×’V$þ•d X0ž[Ê‹EO¸ ¾zª–rü^Vho©îç&  ÞØÁ‚õp¾AÕF8b¸ wq›×þþÛ²+#ve÷¡Aíg˜Xå€jý%sê/ÎPÖì­A›x¼ÁØ 5ׇ|ëÞx5RÔ Œ¾“¡öŸ9â…;Œ||H€Â)þ©ûÝÒÍmÀ©·ùc ?< M;‘Ä&ü/ËQz?¹™ëY, ,*\JÓ4‚Gêáápvïî;ö»»»îï‹>šBžG`žƒÒ  æÊÁ…Y)Ñ9 7°èƒå×<íÈÁì~Ó¥‹^2"?u汿9P«`µy=‰v—¦$Óãc/…?ÛJ9(ƒŽ;Mé€ÖòˆZ¡–'+Ú–FCXÞ(ú«£dqAsÖ7² rÎ=$íÒÄ«RqW. (’<öÈÕÑ×èKC[;eUš–ŒpùF½òîšèßíËmÐW†Ì>UJVldçõBY¼à¨sƒÝlå#U?Žì¸¬ùÍÎÅž@¿jäLzӻ؎¨`yŸ~ÜðÅPKst‹IC/+ =Qinvoke/_version.pyUT *VMXØWMXux ö‹/K-*ÎÌÏ‹ÏÌKËW°UÐ0ÐQ04ÑQ0Ð䊇˂$ÔõÔõ²ò3ó4r 4ŠKŠtâÑtkjrPKH…IIz¸ß]¦­;invoke/collection.pyUT HWúWØWMXux ö¥[msÛ¶–þž_º3+Ê•™tö›oݹ™´›¹Û¤Óxï~ðd$H„,4ɤmm&ûÛ÷¼ÄIÛÙ®¦%888/ÏydMÝvjW7§–ßw§Æ¸/öm}Tù©ŠºUþ‘³É÷yiì®®Âóÿàa2¼ÝÛÛððhÚ[³.ì®sþy£[gâoêª3ÒNýFü~p§Ý'Æ^Ç/^ìJíL,K³ël]eõöx·¼|¡àuvvF_ÃöÂUï•y0»¾ÓÛÒ("š'£ ³Wëµ­l·^gΔû•:×í­ƒ?çŸîñ'/'áëMktg”V•¹'ÂbÙ—•>×èÉ_ 6yä|ŒíMë`º3ry4Ý¡.œÚƒø·½- [ÝÂÓd+) ÊíZÝí+Õ”½£ (Ëš µ\¹®íw¼ouÓ I§m¡^ÿöV0ö¶RÆvPÍN;sœ+øVímë:ÕÔÎ"ºT ”þˆ õImQ° Ð^©ûƒÝTf÷êÖKeÝ@L©Þ™ÕDã¾u û²S(5 a*Õ˜qD†Q Je]ê÷7Á©V›MYëÂk”Ëf£>™Ó}Ýv‰µÀ+Hß¡ìu¡;-V°X®î–AiÜÉuæ¨ÝF[P÷°+^œ”’«ëC²së†ÍkuÛÛÂoçà°Ý‹¦­Ñ˜Õekö—ö¥t‹ôiyAï~|q°¦Õíîpúq#ôy~~ ì±A)P{[ëÝáü<øouiÿX嫪LV2<efòÛ\².Š5Úàæ%¿¢Ø,UWqðg0krOw)L _;u%x™>ˆg®>z·¶ºæóÞAÎ^»¶“BGuI#È¡·u +¡Ì•r‡º/ $ÖÂ[2¶‚éªfG‰ºGô\¿½ v¼ï¢Þ¹të üj±œŒŠ2€O^rLåža%æwu³.Í)g©Œ”‡«¹tÌ·ê_`$°ûäk÷–0«5`.÷j‘_ÙЖòÀöbD,ïÁ«5½‘¨{dõ®ëuYžHK r ¬Ã±;ÝZ„uœ»€É½C—Ka.¿€~ ðn[ ;cˆ" 낵oTka0y5Ƙ‰3I(ó¨×e øC‡lý …ülkÈ‘ÍQoë;ƒF L1ª²o(ŒE´róì0T-Q5)óÐÀÊ(Å y“c…ÀÓŽØ ïtµC1$Þ[Ø Lh@‚3ÚÎŽOŽ€¢i@+ÉÕ¯uÑCÌ ú•¬éÒÕ >4¬ŒU{"«ÚscDÉIæA›Ò¬Hz[SÖ÷Ñë*Û4FAàá5TÑrºûší,±`ÖHžõ0öÔ±¥†¾šºðJ Ž:«†O%@Q¸MQÈ‚RìT®<Ä ´(6F„¦‹Z‚(ÏÞLÀv¼•ä!ëUîíŠ@—@Ž÷:û½z“`Æ ¶)\tAô·ëÀ³ÍŸ½VÑ»úyˆüÿàx?ÔúL\©€ÔÃàÆP’pg @¬))¸x艡S&ßBâbp4eÈ@}î{òk±;z™{bPwWê˜yúŒliîÁ:M;®Ôç/‚Ýw8 r;ðò’#@ÞèÐ`Ü0¼´®Ë(gžàx|ˆ e]Àtóêã kŒœÃøš*‘mùÅ9yS7Ù«¥àêØ?¸õŽ!Ð= Ñ äx<\ˆ'àË(¹ÄOâ9$ÙÄÁKž—¤ð‚~?Ãþ-Ša÷=Ky†(îz…ð‰¤QL¶3-ütÙ¤0™] þ¬ˆŠ7[*uÄs®v†QW´íK©9¡0†d´ªO3¯xùàv1•3d²7"Û"­ç,®áýòÙ¢ý‹e°p‘ÓZm!E‚?·mÝfgïj Z s‰)êçWß´_¾9˱ÚÐ]†Ì ËeÔRkº¾­<Q¨WcÉ®ÁŒ} )¶àgŸý÷Ì‹^ªÏßùqXyÞêWê þËÿ¨mH†yh"‡àá²år™pÑšæq6€E~"g˜?‡Ò—€rfZôÂ+ßЩ%b…‡ô‘—ø;%T,¼aMô³õ‘TîæÂWбÈ:¶S‰©¿3¿\”§iÎŽêu.Ð $ó‚›MRS,uâ){7ÆpŸ=Åd ÷ÖHÑúˆE‹ÎWû+/HÌOm—‡Ä24À)ÛÅW à!?Í8%Þl*L#0QÚ Ó6Pviêêp«»¤q@îýÚ©jLH·+Œ>’}éŠ{3ºù/,R)92§/CXä±è2¢ ]*¼BI DZø"b,ÜÊy½ÆAë5&FDmß!æµ@*j Û.êµæF¨‚Y…Æ’‹ æ*{›Ã¿À¯àzÇÅÄQ7¨ J»©À€•óæ´ÙD¼…9ÉE„ÿÙkpB/ã.í±®ÊØQ`±3+ õØ|åC'eºE]- ‹/Á ‹“:è;l¡p6 |áP)¡ŽËF·úˆNLSÀ{=ê„&Œgõδ-öÐîtßÕ€9"Q:c^ñ%ÔK¬g>q·ªJÕØÖµ0áë&!w€¤œVJ×ïûƒÞÓÓíü§ãÊôE®\X¼ ¸nø[BikÀ‰Mâ*súßžêäÀõMÈÌFòJà°Ž@0ìw;IÀ;Ü ´Éô@I;Hqs´ Ô ú,òdÑz½¢¨­ªB%öPgd‡v p1•ß[*w`gÁò±º` ¡ôkˆÛ¾Ô-—¾#3ˆeïEðª-WÑÔ»?`Õ­;a …}ÝWÅ|þË3Ö>¹ó>!wà|]¶ÈË›‹ï?Ê|ÏO kBÍyGuµš<ï>à¶±…¹SiM2&SÁ buߪú^ºvä¡(o ìólQa¥·½¥*˜«]vè¼Yiqb‰/Ø ÎeÇ”dD{-ì_¿ÿéý%@Û'Ã,êOÅ9ù®„Ų%‚©³G ž!2Á8nÁZô20DoØ.tf¹=àÎóèêõB€:~˜ÖnD¸„Mçá½°i:yÉä@¼_έ=W-¡J2 ¾A‰ó“ùIiÉ5*žÏˆåØt²68£…S ñƒ  ï‹ºÙ¥Ó‚oʘç8‡—8ãÉâJ!u›—ĤƌçÆc:„Ïþ%5w5`,9ÀÔ#º aJþà+Ø„~HLÒü»ÔÇm¡ÕÃ¥tÊ_ÿ¤y§[ç]}™s F”â²Ö{}«-Ô;IÒ'R,êJ÷Þh2b‡³N$çy¿ .5Ç*–Çåþ4‰ å¤óˆ}ˆ‰CDÍÆFá•+ª¹¡>ˆÝ*¸q#Jï1OU¯‹bHÚ7êYn8&Ýþiv…C/6}‡Ž)…sJñ‹â«hL³³÷¡¯ë,H_ؘµ‘ÌB‚æ†Î8˜,Øô¤.„Üro+®F©"†‘—¾`ùíW%ºžœ_$ä ÛÜ÷ÕnÍtæ²3–ó%tîA;'‹I“a–~Î|Ì{áTØQ,Le„QHçSéáËç Èdµ ˤÀKC¶uq‚X=ìo«Ç$q|”Æ×ÉÊs´Ã¸ÕqË_ܶøâˆï[¼!9W5§uÚRq‹´‡£+\⛳åTÆÕ¤]8×"‘kQ‡/¤œ—c;§4ž ®‹Q}PpK#Ô4CSƒZ$KÀ¾ªþS’ ƒðÑ‹)í¨•V»K9}2s½5uzžÑ¿€µoÍ„ð û¹n{BÊL|÷ŽNÐq5ëÖ!$O TöV§Ú;: ³g‹Ï¯¾,0ïC­y «mÍNc^ºøü=Œ E ußœ=o @^Jv•p³\N¥³2ê_E‹›Q¿˜ïNqU6V°Ê)Ë ­ƒÚ–«¿ ¼à¸µ’"#í×¾8?À)¸¨ÞMŽçµËÕO, Ê!y!Mâïñ|RDë4…M»÷ÿƒ)C~q¡Øö#=T–ð¤á9IlÏe· ŸÊ¤âçÊAlU÷&ô|׸…Ê$Òžd½ã1)ä H+…R„_É> uuA¹§L½ë$#– bÁcVªª<Â< Y3¹È_+ŠUÿwˆÂ³ì§˜%¶: ~‹ªÖ5flÞ©ðí#žôžáœ¯×Æ¿ƒÌèO Ó®Ðü&’p‰ŸóÛœÏK7gàs"ÁÑÈÝÙfè; ,u³Ø³aÂr³ùÛ<Í Tg IÚSŽýÎé êlÂÛý#–°ž“e¾ÿÂ[€µè¶Ñ0ŠfËžTŽÜ#7°k«¦wjˆ­Ê¦ã’[c*é|Ù ½m³;ô…É•%¼_S)†×’‹<“¹€¾%Œ Ö$o&-öuou»ÈÕI&Ž‹ŒñpG1nFÎ]¸'¢€<AÚkÜ„¯fZn^}vÇp0 •Mm…%­<àc­•üŽðýÇ) HkbÑL «ö,ìçç´^ätB)šùÓG/3ÎW±£Ÿ–þÈÁÑ(¿ÅÞ"ô€ fèÖ‡uá,° ΋´ŸÇ¿¯4tŠ·'ê[„¢¤9ˆa=å™Ù¼¶ùƒ˜ñ#°Ð7@©­ûÛC¼^â/4 ´5_í¼çé­‚Íÿ²¹€>r°³ëÂ¥XúÛ!2qò¢‚,ë¾¾èú/äìU6Hmƒ¢Ù,ËqÞ÷-u¨é¾D*ÔaZN4I1"KNº…Î]»Cû ϧ"Ï&áÂ'oäÈìT©»ž\ÀB +y’ë`z49x»øøÝ[FѯNÊMèS!D¤’™GZýŒD’?ŠAôyJ{£mdwVsÔ #A÷¯Ô¤_Ã)縘ŒùüÝeŸÌÄ£Xís8o"¶Ã*˜™¹$¸K,nOÓJ”‰Íí Îov¦éÔ?͉,kvó¿h°JÅ5qd:7>ÕŸË0ð,ËïÀÞö|Fá?`¶€7u üü°ÀcQÎqy7ˆFдö¨ÛÓjÈ\&Ub`¤C“Q©CZbû½ñ´ÆÄ…sÝ4¦*²ägÓ‹l”‚YºòWt9çŠu=ܤ]›Þû`DŠÏr¤ ‰¸ÍÖì¯Ã€éUŠ!Û¼y|ÖGˆÿŽ·)¡ 9¥a™Dù¼)%L®>ÎA/çi4ת0x­üc¾)N‡,æ—gS˳ô‡«•ŸÌi5‰¡C‚;X 5ÌM­ÆÐÞû6?Û1¨1ï2؇•úkÏÛš2wa!ä}&ÜT7~Ù_ã2ÂlÇ L%=«Ð™L¦‰gdu[ã-{ÁïA0ïâì}ïËroT½Ìs'OœRCSßÓxÔ7©üÔ7y3oð FW³²àï{¿þ/UdKµ€?‹`Û¤ÍéyÝ·ÊÕØ¡Øé*tÐ-^ìMÒ=^Šfgœ“š† øë…¸îd ¾â¡ Dé§B¦Ëùű&Ãk9w.w­wŸ°jß“Àu.I`v/ævz…iÍýf*:i……¤!=ùâê*j}þ´.ò»+%2{Jwó¤#¼¡Œ'íÜJóÂxªƒùÊSe³ï‘øëiRχm|T&ôðojw°eÑy.“ç˜Mâ=o¹Ñ_}УT°)ŽGù´ôo*»ïlKcfÚ’a#)ñ,ù,©¾%ûx‰ÃV‡»PƒÐÍop܄…ý 'ü )ÖÃCÉ„¨“š9v]–íšÀ½'úü‹¦”äÕ¸ŠXBŒÚ ¡ˆòE)«c¶öxíËŒ—ÄY©.=3 ž?75òt{æ>ó$´Ï—ía±åÍ÷§b¾¿ŒôˆÝf¿SRo¡j:-Ù|ÁÀüìÀT¡ Ý·xO\="VeMøÞ_{8(UîAû¡“¡ï´-ù'Žõ÷]®Þv²ÙÓbw ²ž‚!úÊþÙS¸¦Î¸¾«m¡pºHW‚†‹¾Û9ª°ƒÒŸÜ ¿ÕÝá+úmOàŸØø¡jè¯ÊÓïeRì¸ØíjÌåè¦ßÖttùÖÈ2 ì'”뱜),Ðu!PÅvNh&.†S'öû¸Ä†¤?ðÅiü_¯££K·'¼sÔÕà"ùŒ¹­´YÓw&fŒ4ZÖ‹ÿPKH…II({ x¿binvoke/complete.pyUT HWúWØWMXux ö•WK“Û6 ¾ûW ÎÁÒŒ¬4×Íø”IÒ´écf›ÓNºKK”ÍX"’Z{›ö¿àC¢d'm}ØÑŠ €Ðr¹\¼R]Çd½n…äP©îØr+”„ŽW{&…éLüÌ«Þò¶O`÷$¦9<<¬×Až?<@Ó²]¹X¢Æ…èŽJ[Ð<>™}ËÏ‹E£U%?WüH& „ã×ga øiÃ_k­t<Ò …Üq<ê­hãAÍ·ý®ƒÏ÷’uÜ,‹š71ž‘·)¬`í}¥¤åg´W©¶å9’ß,ÏàÖjqÕ[8jµÓ¬R™©ð½5°zMß¶¤Ÿ€.w „|Tsèm0úÒôÛL¯þÈðý_tvà9¬ X­ ‡`©yÇ„¬¹ö×] ÙòUÈÜA£t¢ô¾|ÿþ{YâëŽÙl<ÉóÀïhCŠ?Ée‚NÝ3ôÚ*¨•³$dÐE'Sšc+&Ê‚®ŸÙ#ˆzûÈ 0ùÈ*&W>õ†RÜÒb'$kAa 1L«]èz˨džu[Î%¨G®OZX˃™ sÒ›¹—K—®Í[­W¡bš Kq“ˆ'!Ÿïd'íÒÂ8w°ñ|È)‡lzÇßriÛ'SZ`˜OµEi «ª×¥V’2¹þoµ 3eþ €z b”¸4Øc7gšî…Æ„¡'‰‚Ÿ5g­¨!‚7:c0¹T®±ãê|tÁê§«•[ÎþŽwÉ5šÇy©û· 0i ú¾ŒC|N6/fLÒÛ?%‘VtpžÂµÌК¹¬Ó€. _;Mü|¤}ï#¾=G¶­UTY“£Tذ¾µõ¦'÷gš(ü< •,åªÂaÙi/ª=œpÙB;”Ë“ŽЈ™©¢I?G4S ¹£ÒüXZÜøÌ½súº@,o<©Ðv$05tKÂÝ¡ÜO¥èïò²Ù&ðW¡¢0Èø¶Jµ&¿ñ%îÒàIë?dyêª÷ý N:…dlÔyÍO’º'‘ÐÉçBØ)ßùÉ\´ÏqA]7*öýâšdDÈ‘i¬¶ jtÅÏŽi\qŠÐçö´E#hX ¯ôÛ‘ýkDš!]¸±ðõ­Þ»”µÆ°,]É‡Û …]£­@«È¸î /aÕbe5–ðDÒð ‰OO+\ƒü9-è%Ü¢]·“MøáAí¿©5‡8"<á›=Žx×ÍîƒÄmX'¦k˜=>NoŠÎYò¤|%¦;ú;[„|pî^¾øPKƒt‹I÷»g‘7 [invoke/config.pyUT FVMXØWMXux ö½\ïsÛ6Òþî¿uf*)•™ëuÞ/ê¹¹\ÒÞåÚÆ™&m禗‘(’XS¤Ž?lëryÿöwŸ]HÊNï©¿Ø&Åb±?ž]Ìö‡²jTRŽg™üýk]öï²>ÛTåž~G‡¸Ù)ۤ̊¹ªyÖè»f®ôÝ!.Ò¶ÖÕ™´nt‘–•m_gwgÙ¿¢×ÿøbq¦èg¬Ý1Þç_¨¸æ?Ît^ëûÛþѵoª£üáúK¿<[Gû8Ùe…®Ž–Ö›²­ýM–ëïÊ8¥Y “¾Kô¡Q/¹É×UUV õH½>¾>~qŠðr]–MÝTñÁR^öIƒãÑáR½Q9ý»¬ùí´ˆ÷z® õY7šeQ6v9"}—ÕM=í5ÂO¥›¶*Ôûg½'7qUOû L'û2˜Ñ"æ‚´¹žÎfÞ2˜9ѯßc3òÇòžY¥ÓÅåñëâ&«Êb¯‹Æ¾äåÌÊ¢¶m~,®‹ò¶€ÞÚ´k›,·-R½n·æù!›MYÙåV?¿|õâêç7gggI×µz7ñ몼;NËõ¯:iÌôÎÏÏù÷ßt~ Õ—¶D"×à-+¶ªÐu£S•fIóYÜ4¤!I¢© ¦VÑó²ØdÛUtÆTÞt’m²$Îóã\eµ"›KÕº$Ó š«¬©u¾™+2KÕ”ê*GE-­¾Ñx5icm ¢Aj™p_’wÞêZM+´UÝèü8‹Ü\øGê1š­Û†Þî²d§¶ºQš~F´š]U¶ÛÎ ²3¥›Äp¦H8Bl)­ku©š–Ä1µ’ÂO’ë¸rÿm÷÷.®—×úØéW£÷µÿ_5|BíÃ2E÷(x(þß æþ¯uCJ·yÇO{HãF»=Ê4Ÿˆ]$‘úÌNr¹|ÿ‡Ëåy]Š›éÝŒWîŽ$¥ ì;6haš8+Â9tLeÿÖåf0¤[©î½ ¯Ô& ÄS“JhWº˜4/+"q@Ú¡ncR“’õšÔd†qnh-—Y‘5ËåÔ(ÄŸY÷ºÙ•©s 0—% %ž&y=WøË3uZZqz3ùÏ"£|—ܾï¨Á™£¿\Ã0âC”œÖÏá‘zuõöë…J[ 4 FŽ›¸,Tl÷"/Ëëö@kºÉbõS=÷Åž–gy®âœ„S«µVÇLç) Ò§h —mnGÆé Fèѳú:;t©·h±Ö»ø&CP«Õ¶,SÖ…–Œ/kHòû²Ò´*M›5d†D3.PˆåÒÌŸVƒœ¼“&‚{BÅÖ£æé±* :R¹ºêÇu¼ò}MÀ²þ—3sF­ßÏYIg¤)éY“;g óÃßÞô#vÌ0Ȇx”ä j™ÏYS@Á`Ù ¸ y#a=¦–¦¬ªyi=…öþ,½ ªËK™ŒÕ o¾¹.ÆV(œ)7R:c1LJ y·§Šðv\j{R¸¤0@¶9Ò(Šà[¼öO†\KzBÂÚAEŒÖ|µè¬î¤v{L¤:ï3Œ~õ­à¨L¼9q Uã¢(¾î©Á=ÈxÏPã`§ëŽGcýxF4³Áȶ9¢¬&äÞÄ%­Ü|θ§ç ,!gÚQ‡¢1÷x³nF Ü4>ÿ%ƒ?ÿð• Á!¶ ¿¾/—suJeÚ"KÊTŸļ·„…J}h!‹ò_±OªÒ‡QoôöêÅÕ‚ý‘è`hOae0bòSG%)6e©6¹‘jLìÑ£çD†F·IǤϛM¶îW•7æÏµéì©«H6Õ¹î^uàeqS^k‚ü‡*Û5ͶŠQ± àS¤9ÂÄl-€°Ã"-“Åê µ¯ŸÝV‚²)4dyL~-$\k›ß²¥õÊäõ²"ÉÛc£÷¢Ò›ÅªÇ\¦«¸Jv’“ÿI^^¸§_­ë ÀX`ïkÇ™(â¾Å8BˆÑâX7ñ>˳˜’ù#ñ¼¶½‘ÀãÇÏ8ãzüXþðeê{Jg×67#ÓÖ˜ çõ‘†¾[,ÎüL6<Ù”åÄøa‡ïOõ £*åÙ+š&£š²"' ™ÕdWœT_Ȩa™…¸‚Fg”é•F³ERY!ÏJÿî¥*o(©ÎR7«·Çƒ­'c€“š-Õ÷Îó¥.nVÎ, jô×RèŠ7 óä …<ßYZs)µè˜XmâéçѦ5µ¾ëvÁúe}(ð;ø„"ä–4\tÁÁÊD;Pa¾Õz“Ÿ@‚/H“I­jÃò)×im%nâ–5Iâ™Éa÷Àøhì'^ÁœSnÑ›}\ȤeLiaíÉ„F,xW:&m¾EÉŸ+áý˜€Ñr‚ì”Xº†µ‘u¥}C·œ‹a±^±’h.Œó\Sž]“¥¬"Ï5Nðé2ÂRS ‡ò¤^^ê$½‚ù6/×qîhM=´éïü ÈÓqo ÛµäD’‘ Œ:|òW]ƒHË"?šú²V룿YDšT‘~Pü†cT™‰7fg?_Ä%ιt%pÐ0raB´7ø›vÍ@ÀÀ°dWb€rŒòëäsv#~l[­„è'!ñ)ýÍžq¹«^~ç*çûÈR¶KôD§ý)E£R·Û’Aº6©Úb²è=äÐtzó Sχ¯w4gzýŠ@ÂÈ[vLôzòdOÖq½›Œ´:4Ç{FØ ×qrMMÞVíX òUàýÃÈ+Ê”ò8ÑKirjMëxßë‚Ò@Z–Óó¤pˆÌKÇûÓmH?l“6¹›„¥@Ù *5]Skc—![5ôž`¬÷.pü ®|¹£L¿÷ŠTo¼™\“íõ[þüŠßœð•ÏI+à¨c ä·6lKüðÜÓâWñ^ü¾wX—yæR6Ô8q4^wš—·@ŒGf> ‰Ô i’ZõØ Fœ¤âÄ6B¢Žr¡ï?¬F†"» ×.í5û|J:\Æ10°æðß*sq›yôÚáûH½á, 'UÞ!¨ŽRÛ¡H*ùlÙÐHL‚àŒ.jÐ8 z{³y¢›äIÆAŽ¢€M¼§Ž…ᘥ´Á'ÊÆÅâ©m(”ï$ê÷òžˆ• à‚—Cð´NOæŸDƒ©¸gf"'˜ô-(äòŠ7-NJ¹6vü|JÆ­C›R÷Î u¸^ÀR&–F‚äб¦~F‚R,×äü(ƒ@Ê"È´Ý’<±ÒnXF†}_Lµœ?ÈêŽÓÏzKh3…dþƒ5üÏá8;¥Ý# åã âT,^“æq–&­¿4©vœÏ»ä‰@Ãj§Œ†#ƽŸ ÐÒ#Ãro³¢°»^<Ý‘:0'Žkmd©— XÐdiHÏ¥— dÃÜÔPþÄaÎ?Í6öÛ)Ù%æHœÖy^wòòÕOWß~½œ¬V1©RHƒ¢˜¡ž¦õ÷ÿxóõÛ·/_ýuµ²çHÍ0êÚPÖÜ„jLsu¾?ðzNÝÌŸã«é{ÿúÎ.„UcÚ½¬jÄ ¬eks“™ô‰rHZôl£à—ÓÒf~é´«wŽz:”Õ¦ÍûüоaéñB6šâž‘ιÚГUǦÔ[Â/…¤ïÂU¯d²RúEݱäáôÝø›¦sh×y–¨g¯_ú›¼oP”'0šéJá€tÏP‘vûŒèNñ¼ÿpæ~îʼnºÝЄ4ÛT­!È9sDóÔä¢ ïÒ#¼Dߥë{©¦xÉ\MàðûpœÌ¼AÖÓö4Àæ/­{tY¸ßy$\Û·Ù¦s $˜}¨’îí%SŽR­¼!ÀS¤{³ÞT½îöÏ`^k¾H+®#x±®¶1rrZ(WøêÊ'äµöiyx" úN'-«+Ðg5<нµ^vµ5 ¬S %x†áÑëûœC¨EwpËt1iÄŸ³"-ok=‚˜<¾(ÂxØÔ AÁçäMwJÀ:°àíñlÈ þˆÆ†p o Œ9X‰‹¾Y¹ÐÿR9ôkÈþl¶ÝÇìj=vXÙ¸US5A˧ƇŒ\õÈuŽ›Ažúµ>%`PN< 9„¿°Ã;µ¼O|ª¿AÅÆðWݧê<"=qˆk‚©û/‰Nx/îYÒþà§T¨=¼œ}z¿Ëb2s¿i)ÇqïêØRòé‹.†ðTU%d öäyÀ©. ªþ>,¦±ü½pô Å#’KµG‰šæaÖ›’d,Psà@%ûCÄÎôÄxN›iÁ1 ÙbïÖœôP’¸l1õ)ÌQù蹦¡&ž€Ù§Ò û°NŽPý]ÔÒ²ø›5s„áÓêçhºÀ¹pÏHõ°Gñ¥{÷ngcà0Q{™E؆w–¦& ¼¨åQ1ò¨`|àM´ý”ýÇ“®OèÛ™ßEßì\}ó©^ÙR Aì]¶Ý¡D^¢Î #†[.ÎDÝ.§¼ÙÏr ±Ø~O`û"ÏHE6y¼D.WbyaVÝ“@·Üc…¿–m»9'»‘Ÿ ÜïÄûd<§=©pkÿÄL¸¥Ö?Óã§+ßÉî'{¸O§]‡á~ƒ.Rï J%Ÿ#[˰¥æö5ºâ%þ&Ø·1û·HÒ±¥CCË–Ù:•tšuC² ˆ¹ö7%zœoÞ@ÞæbbêX74 ŠCœ-¯Qé*°‹ä%xÆÒšç™Ptì‰|Èãœs=¯*y‡Ëe±Wºq¼‘°Î»Ër%)+ÄØüèoиMêqÌ‘CCv‘qMˆ~%GHä„v÷ySv5Z½P»òöDñƒ÷ý“¸(ÌF£)oœHm¿)±éÆ«Là²göŒ„nµ¬e¿íï{@ÈØzëUµ×ívzþC+År»B“u*hDEç'Ô¾Gð–”K9ޤß5—+—~™Ê^WLí¥CúIü»PeÛrôI-úf5·¥8©@ `Âìžšhhß]¾gÅõnõøK÷#߈ò÷ì‡YêÐÈý“}+7–nç“Ï 8Ów”ܵιٕçs£õ¡,Ä#ƒT¹¶éî?alh^ËÓçë¼E™Z,ä(”¹;`Ä_÷·¼³îÄ¨!ÏaQ£¿ˆa~ܨ_d>¶tŸï6›Ïþ…¬w„e¸¿‚cØ1§5mY“o(ÁU²ÃÓջ̎ßNgÌ›¼Å14§ô‰©OIµÖF9 N»(vH2MŠü85åùÉ}ÛHÙ·!M—<ø¾´ÇDÍ!YÏÚøô3P`ïÚž¨mü‡ÝŠ„˜´Tà¿ñ–÷œAÉÈ‹àÑXÎÚ=ïSîÒ¾Áã^Ir$«ò_ŒŒè§£/†ãš7Á³Ìlÿ¢TÿÅp4ó&xÖ¡(ûÄ»`æn¬ —ê·;Ýk< oˆ˜{SÒ®¹’ûÒa…пÈ&-ü›WÔ­+±Ž”í©m7)±ZêÖóçíÂj8àÓÆêÚâ–Z.6eX”0ë‹à¢2ù É?£½ZÉB¬VþiØ~·æ]ãRqãA`&ð%þ’3R}šÐ:£³Ô݆6Îy¿þæ# |è‘O9ÙqÝ•M âµàüfçëÀRÊ,( ÂðZº— éò¢<ßïßDÙdUÍ¡À“â×ÞZkHK-¸ÿ#GrŠ_r¤tñEj®q3îàĵ9m˜Úíq#“~D6„_øˆfÆŒ?¢¥1Á é˺.ó¶Ñ—Xÿt~×I Dºzxƒ;oȺ«Ô6—cCMqæÚÇ\&啦øçdKƤ}ëï7z„í¢ª¹H²*i3>ëkÒ˜ôËÜe0[&\þÂ!æJÇ©wÛ}£BÁ3˜®)‰ðxø¾Œ±ëË!#¿°³}j“ÿphþöBÐþ‘d _q” ¯¶$D˜ÖšðFf½ãOˆ8BG2öhEd~vÙø2m@¢»“3¬âã}Wµ giVŸ_ö—¶?û7þ„üõ­µ&Cɇ»:Õr:,Mõèɶø­Ýa®Lö‚Ý\œ_¶g}Ùùóù¯$7h<‰&×!§í¹ê‹?îóȾ]Ðø§•¯Ëk­&zt•Õ/„µþ5óW¶‚å¼Ý>%Q …‚— öŒšï9xœÑš[÷½Ëêq­F–?û8áÜÛ}e¤ òr[M¹bÀ¡ùýçô`ÆÊfîoêôõ:×±l€(Øn¹Y¨÷Ämn{³kll¹ÎÝûÄÊ”8:yÿ<òÜ-ã|L‘F„c¶¯MZÀ¥_ÖJâÚøý¸‘kœ›—ÇøÂ¿!6Dˆø©ƒ¥“¡d];]²Vüf7½‡Z3s%°ßtMÑáºg]¥QpÉé.`Ä úø)¾Õó)ƒœUôP~ìÇ®îÓ1{Ù-ã3Õ°µnlž¨» ÷ÁÜ}âq4|g`ÌÓ)òZø’‚côF¯#ï~`Â1)_`x‘¥¸"‹ÉšdIsÁl(åœö”¼šú[m¼gõ†ñÂþ°Žûñ½b´1åÎêšÏz ™ŽÅÊ|'êx:Ä3[§ö´GTE Pš$Ù¿ëÏ•0ÔB ¦7(Ïe˜Ã®ñ‹yÚçxëfªñ" CÔ~퇻ð™î'ü£~ÕWŽ.íø JAè4>Q44Õ’ï ÷fÓM&¨îÙBž=þÙûPEx"ÃÓïÄyøÕ‘^þ@ƒÃã#vå‚ÓëÓÃÚÒ‘q³Å÷xÞð_·”ª H~úÔþµÙä½·±…û¦½ùw(ðÑ}5™Ï¸DH>^h|“]¦’0þ2è6wÆÙqµgªëâe4[]Î?ç;÷¤1xÍ_\ñ­ñø8’ëŒÚ0ó‘:C?ñÂ~Ôëf² ‘n§z<ò¥s%6ù9êÀ+ÊtN¸â%£‘xoîÉßúbS=ã|Ní— 4¾Ñ”4Õ«zߊEzâóܤ_xÁ‰K”t”å:*Ñ“/;/nÒ±J­S¶®6é ²ƒ£œÿß@ãä¦P: o\—wa#ŸTš[yüyQ÷}ÁéÉQ.ú BTÂïu-'³ášà fV´a* ¶Æ¿ d&Ë[;öÏöëlÛ–mÍ€N> õzñŸfL\<¢»›Ö½SüÐ}ÒìÇòU”ÕÊ4ç#ßøò¤P_Å& ïØÚG‘YŽÇê'©öÊ.],ûÜ{Z˜z(ߥ´µWù¥ù8Btª{,cáá4gq0÷­“ <öÞÂ}Ùmkjc¤)•`¦hÍ ÕW«÷ü¡„…úŸæÎa÷è=<¯> b»Ýæ¬âo’$ø~|ºñt¶|³ÑrÜ|4 D=Ô:#ãH´.€/0«+ Ôå\¾cÀÛåè?ì'F”é_¬§§ÉË&(÷–*¼|¡ó Ýù@p äõ•›a[Ðb?Ù?}E5=zžËþ<¯YÈÅyåäÆIý7SøÍCß?×wàð©¶[«œèãˬ¸«ïgÛ]‡©†ä‡dÏ<*l—@J¡o—fªÆyŸsùx¾æZ8çðÏîœ@Óðk„Ky°Ügõw7y¼Ù|øœ9ànÀ Ìf¿É]È¡…j¨sµ6jˆZæÙÿPKŠt‹Iú†OŒ2,invoke/context.pyUT SVMXØWMXux öZmoãÆþî_±Õ•HtšöK}qnsŒæåäKÑâŠ\IŒI.Ë%-«AúÛûÌÌ’\’2.­‘ö$‘;œ—gž™eVT¶nÔÑ4•vî&“¯µ¹¹9Ô¶PYùlŸLôlÊÔÖ‘Ë^Tw‡ÎœÙÑ=•5¦Æÿ çE‰-Ù±»õ¯üm£¾ÔþPÛ—‹¿Ë¼$¦j2[ºîίt–·µÙ¨‡¶9õ_¾7®ÂMæ[Û<$´Ä¤^BÝ–¥©ûå_ÛDç|›7þ–³n’SpIÍÊ£ÈLM}ss“äØ9iÙ˜—fÕk¹¾»Qø[,ü¯¿¾Õg]õðáQk]U¦V¿W®ÑÙ’!ZÙýÏ&i¢^G~aìwŠÖ'µÁ’T¥mMK[ºL·PͨJ×,gekØö Rã²Ú¤–·¿¨î\+üŸjD4V¹ɤuP‡.ˆÚZ“yE=uΚ“2/&iÉ€$«ÑîÉ©•3FÝ¥6¹‹o±Ž ìè+½öÛø¡2IvÈ`ßü²QÍ ;`«ÙÃŒëMáÔÁÖx¸7ÝMòÛ䤴ƒ)à¯xÍòΧ ?6úÉ Ä°$¶-+_?vû8äúè6“­²ÜàGìñÖÖ,)9ŽÆ©B§Fi&Äd…‰Ôc£tîH4Ùÿ©ŠÜÊ*fø)þOç¯1ËÓMSg{ØHmY¦9AhÿãÒ)Xª-LÙx… -5 ¢ÊyS=–°7ÌHÆ Ý_è‹ÚñUŠOÍÙ˜Òûà|ÂGñ <Ïb\»ßÊÅ­2p,Bfwº0Ê{Gü#ãÚ;‚ÿ±áAПG¹jÚºtƒg·OW­3ì“‚¹«V÷ª+[‡þ“S¥mE¹29¢RœºZßôy¸éS‚1|£>ùä 5âè^IŒ÷Á0iNõ Æ4y>¬>ÙÒúšÀ:X)‘A L±Z¦9ÙÔG{C‘J.‹£ï¹@Æ&‚×+Ú:í@†þà#vb,Õæ=ù­ê²*#çKù| ÆÇB\sÈå¼9ò9–ű؇1lʘ^_–¦0i&…tÖϸª÷¹¡\8c_½8Èç sÑUÈVwb“û0 "0¡ÕR®/7B)ÖÃ:´Ñò•G‚{²&ÕWóHâŵ©ý¿f*Ï|“Ä‘)KÐ9èG\hðU ›Pä×ÉáñF!•NS Ji`! W/¬Oû°tÒÏ™­‰Á X§B)T%~DÊý#Ÿ pj+—¥Ø(…E@üšp´ÍSβ”ã°€mž½à9L*èÎ@QÜÑ £’‘P­Èh,Û‚Ä+D~m2گĭ(´HäàA¯æ•lçn¸ü‰úëÄ'Ö”FÆBcÄç„1ر ýA›’­þþµð¢»q¹ûñ©kÜá|ùª#•«ÞîFÊr@Q5o'2jQÕINN„ˆAÀJdFÝq<’¥Sk!;S´ÏG,r¸ðE,Ôd"Ëó5ûD}î?Dþß/ȶUÛ¬§;JPûJfOÌÓjß*àÜ• —bÛuõ©&ÔÒðš¾3¡ZàlÁ_Ú,çdé ïó4»–¸îRµU•g죻‚gv‡™CöB¤ð¤R$?÷xܳy!o?¿X¾¥’ˆCgÈíýu©~ùï ôTu¡›• OOS‡¸÷t¦,h5É£;*ïk—èÊx‘ëÍè¶®fß/ Â?ËáùnowTÌK“ß/~°uM Za}DšDX<ÜæéûÒQÐQtSO”éáÖ åCXõsBNW„7H8а<ÄcmÓšæ.½„ £9!Ük+ßb„ææÙäo%sÜjWEÊ®•Ƥ~NÔ} $uXô¥çÙ‹ZÀ`õ„‰›§Av¸õ܉íoäÍeô$ª³POøMœ¹йa<.HãMIÙ4Ok "à¢r$ú[Nƒ:tPšäÔrgüY?Û,õiE(ÖÒØÄó_禡é&å±û呼 k$1Žîúzä.¢Áf™®ü÷á„ÝÝ$޹7dÙÜJžmzQ÷C„ a·Z(g7§Bæ™êÝ´ ÖºtõƒþXXœöHq;MÊŸ°‚ODõ¶Þ˜Q%²V†DâYÑôù}Íc}Rœäyi¼¤.ðè:K=‘¶×é‹@Óƒ˜rêú|çKwEÔâdò ÕÞ6dÛ¡ã’"KÓƒ%OYò<Òq®¢ÊR! ²’žæ~0}ÝxŠ}ïKÐLR°êw4qé÷FàÀ3väÏåE G³N0º=ج#Ô/©Ž²ÆjÓqî&ÛŸE}c“§î<Êÿ;9z‡®g†oa¹nÙç“ã.I Ÿ¤†jgê{¡uVè:˹Ò܇ɔa ¦ùäo+ÖO,(È~7.‰"θ»a#ßÈÓ9Î$kb9‡CCÉ'4òˆKfòTz6Ùt'!ŽÉCÕyOVEçHãX:ãHÕ º$ÒáñéF‰BðìÇ?¾Ï ª,°–0¿Ð|¬»T2äá«(î?¸0<îÖÿÃùÇGçwå³>žt.‹·ÜïúiùÈÞ@±7Zl8ï_¯­<øu×ûŽXTýü/+ÆLÓ>šæÏNÂmŠ[›0¨ÒA×ÜÛL FnYd^ÜØ‘0òf0·N½ÊËÑ„½W]³L)é¯gý9šqü i@‚GÓÓõäLéõ¡ºfpu“¡ÜV ÔÈÍÌU£¹ÄÞxÐÎFQ^JÉÇ炯™ñUq1·¹vïhË(sýèÇOågÏFi­d(N°8ü‘£=HºíQÁ7Ⱥܞóùó,:án黟à œrðêµk òL§&ÒPóM}eZR0ÃÇÑ0[æL2%–„òHd–s &{= ƒ`ã±^¨€ 'g˜ñ—ÊxŒâšìä)8hŧqlOŠqÜ¢=_¢-Óì9K‰éÌr_ñ‘µDÉ+'=1ãîaRf•V|† 9"-ÊРܼvCË‚©é ª8Ú¹ É`iõ8(è3Hª’1?V$Ÿf)jsâé|t¾HSý»Ð"bµý‚¦[’ÇD~Ê–V“PR\Ž@½è¨ÑÂ%6µv»åœ0¶MH¼úu¢Å| ˜‘å`^h*@Gµ\N©Å3ôÞŠU÷è‘+¼ œBFppA¬KHqèìËá¬Õ/Ÿþ®þu1!T¥Sâ5]oØïk½ïÈ™†Í%…n±CWÞ÷äâàuïa°Õ߮З½ÙÉù‹`(½ ‚UÚŸ —ÛQ]‚»•p‹ÝŽr·S¦IÖï¼¼@90n45uç঩ÌôE"MʶÝò`¾r­L¢JÏb¡ϼ¼´3WóØpχÍ.C©7Îܶø WÇÚži¶¿Ëm³£$éŠòФ½4øJïkàÝgoýبûAfk©í§ôGøšùS oæàTpÇ~݉U½åá„RfÀgòÝx­ÎŽYÉEï¤S±8S{ ,Q 8Í%ÃÅsžå8)jm»'²å«ŸËþ-‡%ü¢Mbªû âPÜ{¥æå>:ìè噂Æç wŸ5ÎÒøñ°5<¿ý)—@«72‹Y’Ç–{CÓ¦¥úìåêKdÅ=áæùÖÈÞ@„—Óþñ­ Rè=¦ÓôDhÜ~±–Nê+éŠ{;˜áÿáöÓmæë8OÜîÕx%.>£É¯(<Ï£Ôoy?b6ßë2ÚÑG6rEð§WDÎ úªºc‰³Ë©É?-Ýi½¼Øë!“–ÕC÷úÉ{é÷)€ýç¿™ š¨4‰Wr¨kû¬¾önÕÀ«CÚ¨»¶‚s£—nšöÀÜÉ¢^2½% ·"¨?Õ äɾߩ÷ô.Íì‘Öƒ¢^dë;õÁ:<5Œ«Ç齡§L9´ y¿eºFÝ5P£ðzœ 叿ÑG3M8ŸîXÄ€ áôlŒ†ËM°ƒ9toß«ïR|ÔÀþõ-r^c°í²}2òêPc«-Ïd‡ÖÈú§¬= M^=Ó:Z>\¢YðÁиzHtñ®é4Ÿ >Ò³Ë'©Q´j4FõÂP XÚ¢\§Q‰N›Orý»eüMh̰jæQD"}ÀH¼x¢ú›¬OûÍÿ_PKH…II@n÷ú­ invoke/env.pyUT HWúWØWMXux öWÛnÛ8}×W°°–[EÛ¾H ëÝîöEÓ LKtÌFµ$• ‚üûž!E™’Ó¢ÚÈ"çvfÎp8›Í’Us+µjö¢±ì–kÉ7µ`¥j¶òºÓÜJÕ°ZñJ6׬¬¹1y’|5ô‹ûßl'´`•¦™[¦¯ë¶W•¨o쎶n:ËöüFf,·‚µ£ï©lOŒ´7¤ÅÔ´"íJ´óäV4ÞÁð—4¤»ƒ‹x+;­±{ØedG*Öjy ¿ó†É}[ Ìk¯„å’¼ª˜Ù©®®’FY¶ª¦¬» ²pÇî»háõ=;ÿô7â*»ACžÌXµJ[¦L’lµÚ³VJ³þ»‘÷aAÜ—¢%I¿6%À¸ã:cçû VñzÉÎÂÓ^¦›î:Iw”¯Tm¾‹Ò.– ÃS‰-+ ÙH[©õ6ëÓ˜±•÷ý>zh9/ü2;ë÷MV½VýËxµâ–céñ‰°óu5·J'ƒ'T3΋È*¡Þ? ÛidŸ5ÂX@_ÉÒ’HQCpËëõâÐX+“ ö:O­(åV–TqY¸Û)#Øx@¡ð–YÅx¢¬Nou׌+;ŠÉZX5v×êŽè ·Bo•Þ³ 7²dö&‘?¬Ç~Á–Àh¹†“§àY3õõÂ<ÿ û¸¡X½AYâ;}ËĽt6|LÇ †]Yö}5¿«SÄ[´ÜîÎ.¯²aÏÙãÓbÜCbvQòÆ¡Kîõ ãe©´#8ì’½do^è§Œl·XÁï×ø}pßÕdºÏ nÓ`2ÕÎb…ûn'Êg˜—à{=œîT£é{)ÇP«È$fÐÑ;M†2"&΂q¹´BãßÞ ¾D…Gu&úÒQCël¶`¯‚î‘”ÜaéPˆcåkàS¯‡”d‘ÐeÐuµ˜ 9óÕÕ/Ô|z _„­öT–$ì«Ã7ƒƒCÏ —æêžïáJïãd.} ]¯ƒ–õšý,·Ê¢ÉCNMÄŽ¯¬\¯ƒMÈ9ª#·à‡ÐÎPæÎ\MK®ü‰Š(Í%ÉóU‡žJRFåÌ N'þª­ëà[hL¸-—É([G¹›¯þý´z÷eõG±úð­øvþ¹x¿ú¼š/ÙåÜWëúö”LzE£à¤vN#ŽR‡ß¸£µ\' µÆ7êV<ß@°=t‚ǧá+N„¡5¸R¼ŽJ1¦ãE·9u ¡ßh“ÔˆaÀî¸áÖj:a|¼fîy9^(H?Ñ®(æ¾[!çX³ùž“ŠtqÌš½Ð×¢ ‘GQ3yù2D¹ø‘ ëg‡vðŠ]âýêh»cè5’,våØÜ {ðûшÊÔm‰"î­‡ÛÃÛo Ñ=¿¡©aÍþT°ûßéR8S¯£~<}o¦³E m¡‘ÀƒÅsQþC ¸LÓa,›Nm ^ç]‹v#Ò>ظº>ºúFi’µàè>êË,>vEmÄ8ö ûÒgɪ¢¯†C_EéžöÀ µÀHŸFÅØKÏ‹yþ]Éæ` !‚†é"Ò7°ê‡ÚNØ_‚˜^Ýø2¶6 :¤Æî¸Ó¸Âí¡:ñ¥;"mþnªß‘ô0áu4Œ„Ý1ÿ'S]TžÁé1ö^ÿÔƒƒïSÌ ý6ã"€áŽó0;bþ/Ç(Ü · §²¡ÓÄ#¨£H•êT8—üðëJÕ¡×?‰?Aáryúæj EOQºøþuÌЀӸÿþGUW¿Òš©„)D~S\“‰uï?Ü'7H2>ûIÖ§Çi å(1Q" ¸Œ ÞÆF©úhjxÐHç¯é œ/"?£Œ†2ci®+¨RDZAñXÁ„®ñA5â%¦–ÓóCÆl‡Ûßbb¶ï§ï8]VyÅ[* &¢;°÷ÒÕšì‹Ù3¢NJÑ‘áI;õxzߣ7ílu•…»ðá2ç2—üPK—t‹I­Mà‰¨ ¢ invoke/exceptions.pyUT nVMXØWMXux ö•YmsÛ6þ®_ª3'©•™äî›R÷Æ×szùФ“¸ítÒŒ‘ÄšthYÍä¿ß³ ðUv’óL" \,ûòì §Óéä‡ÚyS uŸ©ƒ/ŒY)S.™LnöÊ)q'íIZÔøžIü·µ Ÿ•ÐJå*R¸ƒÊŠm‘‰\züò¶Î|m•(åÉÔ›'-{G|r±5VTÊ9¹SœWèÝTx#\QÊSŸ;ÕýÁ‚VH-”µÆN2£ó‚……\RåI(é lô¦„@i}Óø½²K‰ð°Öà¤2®ÌÇM“É*˜0ifÊYFHaÀBËJå¾>”*x+3µ‘ÙmC€kTÒ¯ÛÛ²ÃÁÚ74‡@OIî”ÎqùøÐ÷“É„U.~hxeü Së|~Ý0^¬&¹ÚŠõºÐ…_¯çN•Û%˸„ÎqåHDô,¡Gâ’)†˜Oø³=ÿ…,JXíìPR}¶ëÂÕ›°Å*²ŒÒöÛ°_˜-l’™ª’оWYM›àOÌ,ž2¬V)Ø&‘“Sá=p{U–0s^d2¬ÃŒµ«eÉìpz]z1ç•ò´ÄÉÚè‹¿”%·)<çj±¤qßéL¥üž„V¥ƒúÊâ6¨HŠ4uunÒ´é¸/²=| þ·É­¨ µ«‡²À ¹ðÑØœu òÀª 4¹»ëßÞÛbƒ[‚cYš#nç­¡båÂáså•­ ­ø-}T+ý:X³)Uµ ü¾¼Aiº"é“7á˜Âº:SâXø=~m Éhì«{£ ƒè}Õ/E±%…YÉò€1@Ò-Åžö¬\•'=!¤3š„HÓWF«4eýöð[˜3NÖ Xný©‘5J‚}*Ù%|·œ«ß¤ÏÈ)*>Ò¸pMaœ6ºÝ. —À£ñVZżkóÏEeœo4ê „ Wk­lbk2»9I)ŽVÊ­ ‘VÃà iòƒÑ^Ýû„ýe=ÃQù¤"2§6\ÈÍDúK C×PbJÚH¯à,12Òdu„|08}’Î/IÝãÐqqiÇi?¤/“Þ9ÐñœK«àš€=<d0¹‡ÄsÇŠÔ_‹›×ÿ~½B¤Ý’áõ¶ØÕVÂaO4ìUÂÑáßHäí€Fø£û§øIíñÇ{è ˆ:ýCÿ¡§â[ú2Mþ4…Ž'!èi»›/Þ]<{ºz¿ˆ‡½z}s½n%Eûº9›ƒò9§ X—ÒÑFQ,æø&àƒšp»,þŠñ•@ò´94åvå‡Ïl}ðê$í{`àk¡(`p͹‘y¯jˆüÕd|%><ýÊ~œLZçÆÊ3ü~˪]}ø{øŽ³Vþï8 •Ú@œxÄr «Ë6$™Xè¡ùc˜ GõAS=-CêËkE!)7fÆZ AW™4ÕFă$!›©U§æ® 0›?±ÕQÍ!‘0¹œö\Ö÷*r:@ ”Oˆ_Ô¶­6;÷ÓA3\ VÏÔ ±SX‡ ÜÖÄ¥­$.˜¬«Ž~´!ædJýpž/Îy(€ÿWº [ð0|™|(b$žµuRxUx²ƒ…»¨?ª“éØmÁkàUQüÖS~–Ö)®b-»¯b×#¤-×ÙÜŠ°t–€³ÑœF·¥Ü¹'Òîê 梛\U$cS£:Òr}‡ô†IwËM‚ëÖ˜|ÑgŸµLåvKJõT Õ"5j§ywÉF‹¤eÍ‹¡µ"'X ~kuÅ)ö1-½¥ÆQQG-¾(£¿=Á·*¿—^ ÛRû)vÔÆ5®Zø¨Ÿ7ª2wÈZ¥ñ!u!0ظ“KBP HHIÌH¤(á½Ü ´ð§Ïª‰êòéØqi•¯Šhk}¢”ž|æÓnñ&ħH‚ ôŽ;Y …@1YV[ÇŠà`­"èJ‘{¼õ5¡Íoè¯Ì‘[sìðöD^G§–[kîK$ÝnýߺàÒƒx¦)2QÊåÊäuÙ”(7-¢p·Ï1Ãu u†¥BUn£SÇ!@Ä%–`¨J ÁX¾Öw¿Ê/SNidΞ¾£YF¬JÅ­:QŠä©‚lC ñNùOHð‹Î$½T_& An„ §J.Š@"9Hg _¸Š$ìBÜÕÔZÒ…MgßDQœ¯·[¨þ§ß׿^½¹œn™¦ÔïáYš~˜U§5Θ­Ä»Y½Ÿ-ÅÌìgï?F+åF9=ó¡>—ú/Ôiµ(Ÿ  8*ÁDÀÁ]2þ³¡SBÙêÈqàb¡¡ý„Žnµ9êh…á®[lAÉcª{y/å$ÅØ;©8Ȥ¦úd£X‡*PНW`ÝÍnBGÚ“ð1*U.ÐÈë^b žÎÈ -v‚ÙOhWCuŽÿ‚Z—òGGK“ö¢ÿ¬ÔpÜ0ÃTÌyˆ@¼šÖ‡P¸ð"¶KÄ ¾bš„]-³f;Zïnó,I^qJìºÍK­Ëä|ª Ë‡;Ǻ›òZ[LZî¿Å÷²§±9ks6¦™-ÃúíúqÁDì¶Ý`l6YÄÞ‹AŠ“u Ÿ‡q”„4ÉÄ[[ „ð(%Cض3y¥i`—Ï‹¬ïë{ˆï KRßÂÜJ¥i$®Tµ!t ª$Bœ‚ŠÇÀ/PÍq“êÄEè992iI“CMZˆ½ #RÇÈïš»B‹>òÁ. fDuqŸÐAøW´A´ŒíLÞ¯ûÁD\¢W!òÙ¨j[Þ½¬n¹TØÑ‘Ìì¼úÖê¸&ŠK¢;{Š3‹4¤šã1ðc½.•^¯gÁ=ñÖâ{4zç¼éI ¨ïT¦VUÙ5 æ5gš†ÄßOd7÷ÝêÙÓ÷â[ñ‡¼?—I¸1Ìçq[W^´Vz¾;¢î·ö-Ad£@|È^L(—;¨«¨š:—œ¡ÙYší¶3KñòÉëE¦í(KÑp¦Bu%C1y¯óÆrý2q÷4팡5wJÑÜ ØŸ1¨Å~ƒt?×犺1÷h[q¡$„P^äÜ+ª¦-Ìô–8ŽS¤ 9¾p]¡ÝâohSâз¤ÍMÕ€.-èà–ÃdÈhÐHÇÈåÄwã¥ïS ÎÙT3~Á"D4D€o¾Q™¤Ú( 4`4HßÌú€%noê2oØÉòH“™â*²™ôDhÇÝà»ÛûžfBùÖæ¦–›#³ji¦¡<ÍÖÔ¥!W8³bhŽÐ³ìÀy‘Äí —¡‰±ÐøZeckA&ë\Šà•,sTey G¿‘![VgTaDaåPÖŽy$^¾îoß èã˜u£zp¦Daö°ä®ùÎÀ7÷õNõ9Ôš`” O¾]—Á‘æ‹~§8¨å{){TÏ?À¦GüÙÖ3FàÔÉîÛ‘LÇn¾_‹ÕE™ÇüÔ\n@¸‡Þ–A¦ AÉG®~xöqú¹kð4ülºÙ3诹æç‰ÿ>‰^¾XžíꦹãWiؼø>‰~Œ…Ö“G¬ʳ{]Š¡€”´FÚq˜.E$œ‹•ê¡õúQõð€:°ˆšëQ<4Ïš¼•GRþ e„4Ñmsî1™Ð¸ª7¡ú†õÙÜý·!&©•FLg45 H½ðåkŽ_·´EWx]:nî–ènš÷RÉRó¾Î=ôØPdqLؽˆ“ÚT² ã Ê$¦ö‡Ú‹0äï^»È³ÁRÄg†XS´(ióRPχ»()¡mœˆñRE¨‘&Í ˜þM÷¥!ŽÐ|òX$BÖ‹o”ú­·óàþ„j0Jh¼¨2å÷o¤bDóâVû=Ô›x«WÆ_e$Êç}3ŸµRA €í'Ç@íµ CÌ6èmZuÑ•©*àÉ–sÝ+Ê~·Cé…Õ ½iHY]¢‹#Ë©AM@pþ’ÙÀJ¦›š¡Ì¨Q´ó4â_´Î8=¨ãPKH…IIº6lÚ‰ ð invoke/executor.pyUT HWúWØWMXux öZ[oÛ8~ϯà¦@cgmu;Áf€¢Í XLhûP1-Ñ6'²¨%©8Þ¢ûÛ÷;‡”DIv¶~‰-‘‡çú ³±f/²ÜT½z_ëÅGþu±i_yõâ“wô3¾¬¥uʶïþà_Ã×eû¾Pë¦%ë¥{rQY– qGñõ³ª Óvúåââ"/¥sâîEå7vfÖªÜÏo.>———ü÷C%/ЦÎ[éÕö(6 EÔEØã² ^ü¥Y3MåÄ^…yVÖêB‰giµi(yU9¢T]y'¼ùNV[µ²(¨ZµÇ6¦¶V;ù¬Í j#u¥ýããÌ©r³¹)KpºôT}û»©ý°Š¿F¡R:ôù T´,õT ´ß ðT”Šù«T®œ“ö( é%© É}cU+3}n`6¹OøècŠUö±{·S‘.yM ™ÿÝ(çé!q†Ó…ß)mIZÙ”~@.ú±³z#duœ‹õQTr¯DÑX]m{›â’¶8¬„©i¹,™WZ±;SD,ìh`ûàðñ)0Æ*’Õ€˜Ú×þ˜‹•ñb«áƒ§Ø±ê5f1òd•¨­Ù i·Í^Uä‡)¡”ÇÕŠü`µJ O–oêfÈ}îwáäDS±¹ _X¤µÅQ©èHÜnÝxvùµ"ënšrB<¸><;ïD.!„\lvµÊõFçA°N 1V+x :U˜ óÈB$uU(»ÊçÙIÿ§ðÉz¯·‰ ±ÏݶΛ¶ß[—´+T‰ƒƒÙgó1ˆuËÒ]tqQ1Œ¯ÙûÏ„j@)Ȉc¥{"·ZñŽ\ ª  ªr5õ0^4q1 ä²nlmÀ²öÊÊuɼ 1sEî.¡@ cíÕ~ hÆ6Þ€™Þ˧È^0ê4òU å v\_ ëáõõ5E.­“̪`À& -2_¹!€dìœrûÄeØ]Æ ¸#~¾Y#Tj«)r »sHO'Ò6WË\j¬ˆm3(™Ü¶;œó mC(ï¡— /ò䨰!-ÂSrð$F'Šð³ô " º8ìÈ"m‡?qŒ‘EݰœAo€É¡HÏj.<§ bH¥Ð9‚­ÑžíMº ÈQZ­®¯Ÿà˜|ë°SU§02 ñ :wPÑÍÈ´ôù:yBŸÙízµß¾Ï¯,ù‰–\÷W7âêY–ï¯ÎmȲlòüaÈGphšf»+ÞœbYLÂ7}þÓŒxº ͧ†Ã5ì©+$ Äå"~ÜGH;®)¨ fÝÈo¤ce÷Vïí Ò=všÐ‰{'.-Â8ΖB¤P:ˆ½§ƒa†u7ÿálñ¯ÖIpzçñÕ1ré rÄLg* >/‹ø5!G£,:_ ²NˆÊ!vžÐ"ƒ”,¸V%]©"I«PÎTc€ ²p°‚’Z 1'a'ª»²Qã°æôiÁB^6%lµdCvý2–¬\ÁˆƒJ$¿;,*^$¥ÜA½LÒT¨×$²ntY@ Ìk¡j¼Ä:–Hm§ìÔB$“.>«Õ¢Í¬ØÖÓ;h˜ õ§ý*BÈâÃÄ“:6)°ô$Hâþi<ítJ~ƒLj÷¡&ÕUÝô'q±?»¼{¡dÁF1µ(Õ³*#ûÛ_ì÷ËŒ²ô³¯/|Ü ñÌïæ}L“§:¤cv¢ª=pðøÄûP”VæÀ}…›C0ÝŒŽeÊÉIoÄç5'¤ÜÔGJ…¶°\y\r=YwpE^u±S"q™½òÙ)¡Ä ¢Ao6p– DzV™*$*µk™HvÞ½ÔìuV½#Ï‹§¾¥‘*UÑÔ|ºQ¶©²d÷?Œ,Úraìɵ¹¡PØ*/JäUPå@`ÅPÅöJok¨¤¢ÊòŽ‘ ­bNá•qYøýȹÚ'•ïW0A[cŠÈYÀÜ›4Þ—n…Q®ºò‚´šcصJ!$‰è5³¶¦Ø«¨#¢Ø#$€àpƬçÆÛãK"mí:Õ,?EŽ_µsˆÅ\üULhG¼‹G“Åf×´Uú5? Ñ?$OHÞŠ1M‘üx÷½÷Ÿ?}¾‰]w¨±LµŒ–É%,óŽŸ:à5ge‡Ë ëox•XMð¶ù‹'°ŸßG³b»¯ÔPAx˜xg½¸´ofz íÌkÝ̽••#;Aók&Î_â𮛘øc­%=ja3‚ßÕhæBŸ/ÖÂä±.æ,ó߬í²˜TW€Æ¥;|Ú`ùú0pmæ¬M#CÃR]´uÕ­c—$2¢[h×–œc4€Ê»,â|ê/\ôÝò¡ÃJ <"öƉ'èÓ±ñ‰–T‚£(”Z}ñwŽŸW'æHŒ»ÔÙ§Ýü(fÀjGi>VµîT)Œò·;wt§X²Ãȉð܋әe—óA„½’<(]Ó[²˜n‹•bê…ñ8Î7X-:-:ÑT¨ÿÍÓkÉ!¤s.Zõ0<›O#^Y‡²´h>Ðmº'ÍÍÄ9¢ÛDÒ!QR%îÓÕAçF\òÉs– ˜*E¥¤]¢)BÑ®¤4“¸ÅýÙ¹_õ œP«îPx9z}¤Qv;°¸ê¤->—=ЯT¡ÞÉ <¶ïvµR¢‹í†Ï)؇ìív”Ñ #¸Ë!†ª­ãV¥(4³9£ÉèÁò|Ðsß ëÐ('î!¥†m[¼ ˜ÐÆÍAÛžV!í¾;]Èušw‹8MGfWV®×“ò&I;ywÅr"Û°P)êO#©«ÞÈM[hXòÆÿ[Ç¥õ¾ÍâÝGÒ^<‚Fä4˸òúýóý]¨¼ˆ*—¬Þr4„ ‹Sà û†ÿ¡‘Ý…Å›±‚wªå&8V&þö\첪$òHÈ<Ë}G7.ä’‹b‹¥3âÏÔòRÉJÙqùæ3¾c*f§›§ ŽqF´÷,ìýaˆx‚rW,úÐÓôp¶èô-Qû÷(þn‘@Û3ðö«‚ô!Eö7-Ám‡ãM2Ißj×9™q­c#ŠŠzY a±ƒö¹ÜW–á]ÖE'HÍ0Æö<Íì%ÝU®±qpEÔÂB>;Þ¸«2ÖÃÿÖ^µm ò9¶Ãaè'sÉGæ’ BÛC[%Ác‚PØlÅë‰) ÑZs=ôå4?þt+‰®œ²~õjÓn]Ï¿Í|#ºCe²ÖöÚ–fRÊ»™ÕûN<Åñà‡LûMЉv›ìh…b8s¶ï½™ÎoNÌâAùì$Wëÿr™á1Êš²)„G×CÅfƒÉrŸ{ŒiuKáJ·Zι Žã>Öáé¯W/ìßË$çs‰óÊzè¤èÜÁÇçNSÄÞñwkúenšˆšzu)Aaùóõ_Fø¸(ajFkôb;<þøåæúö?]´Ò.NDÅ“t@—IÜ­p-ó*è4w2`ÇGÚ8°í¿FƒÉ ¶ŒBøè®j¸ à<0¥B»pºw©ñêîKa F c€äh™™¦œ¯-ƒ–/£Š°4ö«Q’—¸ŒàPŒÐ¯šõ'Œ€ØX–0\7:4ƒL 'ÍÅ!ˆ+j+í£[ioóšjSל!Ðh~3kD`~žÐ80¬õ¯c”ãªMc+ݱ-ÇUeåçæ7á–À—ÿ”–ßx]ˆapg†Ç%R°ÝÆŠD…ŠÓ³øPKH…IIܶ“‹Çinvoke/main.pyUT HWúWØWMXux öMŽA Â0E÷sŠ!›(”Ü ;7îÜ‹¤‘NkÐÌ”iZéí-¦‚ùà?ž1μȓì„òf´÷ÄQW‹ÄE×Qp’¡é&,ÂvT4æ³tó‹˜Í½JF‡)¢CXH§$Bƒ—zØŸèè¸c&oj‡i¾¨fx›x¹nôf+Þ¥þ_GøPK H…IIinvoke/parser/UT HWúW^XMXux öPKH…IIàôK›eÆinvoke/parser/__init__.pyUT HWúWØWMXux ö•ŒÁ €@ ÿVð'bbv As‡à%šD°|E<þüíî T4+ª‘œVQ‡  – ‹pÓQØéðŒû[îžñ‡ŠO¬Áe Æ+(²-è4ì<‘Ú(JöyE{"~oÛܳvPKH…II ®ÉßÎ invoke/parser/argument.pyUT HWúWØWMXux öVQoÛ6~ϯ¸éa–Yi·7¯.lí6 4À‚B¢%ÚbC“IÅ1‚ü÷Ý‘%%N7?X”xüîî»GÖ’Y Wf×í¹r©Þ|åµËV€¿$Iüó j½ß3Õ,¥PXo|¹•lW\x“Õ¶Åö<¬¥ßç“r¬v¢Û혭6PUdc×·ïèùþKUð»¸jíÚ~¾ªÝE ~~„ºgR4Ås¿vtüIXz ÞDƒÁŠ­àÆú\‹(C|ÄOüí’çÀ i¹<$j°ƒ=;Á†C÷ÈAGÁ2ïdﮪnËv‘Ãb¹$”e7 óN¨fŒòætà°E‚´9Á÷€&–hõ¡Øˆ‡cLú(¤×…¡ûX'8þà"fÚñÑäH‘ÓÜõɵZÑßqó3QŠ˜­eåô×òˆÓ¡4âk¤å€jÀu€QvLön6Ÿu†3çCŠ$1$6b9½ÛIÞ«dÂBŸÀHÄo}F}ÏD¼gB²äÞQˇÀÄcë ±Š;qÏh5Ë¡+XgŽ©&£×?ðÍ“èùâªÁª’<:ËCu«*”ñYÚ '´brÄú»åÝS7UØÂÆÄ¼‚BÌ#Š<´^MÔþ‘IK{ í©Ê"˜…}‡bC¬µpòäE8ßúð"L•VËA£0üŸNŽ)„ÈgÐÌ9SÎ÷ùUTZÜi—d&P(¨p#Ü>ÌÜé jʶ¨@ä\(O÷(¬v‡Õ0¶Öçïq„ÉÀ9Šíárl µÆ.ƒ ³-·Å¬s!P–B W–©år›û(ÖiÅÃЮÓ,÷smÉqöTþ0wH,ÜÚ—)dï‘!¿2y"ñR¿ q>ëXôÛÛ¬!ù•)ªÉ$´ÄÙ.ü*?´‹QßÁµ¨ïpð"™&PÇÔb>£MŠàÙ,t‘úhH„š­¾ \©xL¶ #Ä 9£þ‡'áÉè‡/<8&æ:ì°ixë™°À‘±Gž=[G5Áeô˜Ov,ÃŽZ‡ñøžÛÍr=v>MõÅ9zÌ'Æ*ãôø27 &Ãpn•€q|1Q&J®æ„}…%Â’dZ°À%NQN\éã›§,)°í™K“’â«*¯žPm¸?X’w¸poŸ|züéé}Ę9 t—5]ÊGW–ùK#_Ò—AÎ?%?$1µ ã^“ì³)e†ÎpÖ§|†™°â—ƒÑnÜ)®§°ž¯M&®n°ùÔ û"õ(ˆMl9kbgnc£¥sûsUÅŠS'Ç4CãÇó ;ÚÑÁŸ‡BHapyt1= †.§©{qKgãòŸ«±Ç-xûæË«¬ ÂxÖäíêUÇî¸ {ñ[8~_#wÔèèy î?úm?¨Œ·B¦V„4í½¯pªYî7güåT׉S€Ö}n8™£H¬[ߘŽOÄ9š?žšž×ÜU¸[ zX¶¤èèV|uý'Ðy9‘ØgŽ÷€ªš7CÔJ«ª†—Oðo¯¬›­‰uIý笪r<‚%·dîÃn$m[Q·”÷Æ@Æ{ZkºH5çÕú¢ûç|:Î¥Õl=™¡®’í7 ƒ‡Á¡ä“hó¯º!AS’DÖÛ œBPdí¤[mU5s«Óú)j‡ò‘†kØû‚UÒ2øÎ!2Éíìõ€~G…”ù e&“ŠãÐ#FZøüš_DZ+èom;œ•žptøYxrì(kPuø4E4Q¢•°µÞl<½¨ ,_…7>ì·º9`õÈÒ¬Pͨ±T¢Cm/õ¦3êè™ø åÝ*Y#Vw”(CˆûÜÉZ¯54ì¥Ûb媖֊?$ìi_›Æ!— ³ü¤Vi 9oå vñ]cjUmkàÔþžâ©[ò#"W^ðæßT£ZÎh3+š­¼>qßšM+‘Ã-ŠÊI{¶þg«àœ®ÁÊÖt›-ÞîÙÚ©ÏPΑ­©+Zä4:ý¬;øe·Ð‚0.¹Xšß:±ù\7ÚÍçJc=eœ¼þ01…|²×ÅdJ8O¿Lú´ˆ2èßkøÛ¡TD£Äb1ðébÁ2+<§Ÿ‹ÅÔ±ò‹p^úQ ›½ÓÅíME(Öá¨KRã+špƒ hJñÆ-!n…¦™d9' l>¾/xÃy¹”õØIÞ’a5á]“ÄT „ê![ÔüjÛ5œXºÙ£ôv”¯°‹ÑMNz£†šGLg¥(Æ£Ÿ´aÚ"—³$ÙŠ$)[™2”Óbkʤæ1Îð¨½ÀÞ¦E;“´˜Þš”Z¸ÌìÉGÞ@-„R•ËgAbpL·zµõ}ásG…'kXÞHiFzæ¶/äxìfÇCÈ• ,eUÍñ'S 8H®©±P†jœæ/In*Ïæ ßÝrï.àOz»7™äçyXújZá ùI¾æqÙ—¯Ô„ Ò^ùD û‘ŸáÖÀÜãêMAºŽáJ¯”^ô{3g]dÄl¯(óqœÌŠ¡Žl‡(¾¼ø:•Û â©å'£›"ß=éÝŽ[ŒÄ—ßµ_¿¼üšö&w$„™L’¼ÛHfdV1ºòÒ†¢èm&Kfôw‚?„ªÄ™`ÙOI^°Šæî??ãÏÈ]ëßô;bX<¢^’<ü¸¼{`Éç!ôUUÙ‹E2P5eÓ@K·r>!<‡DN¨°ˆÄÚÍÊD&Í*”AŸÑ(÷¹D«Š‘Òó9S£¡4€‹ƒx8€ åBv™ ýJŒòU üX¡ã`ºo"IÞzO´‰9UºØ¢ó©*OUYnIKÄØ– \U¬‚~«õú”¤Q(ÏÃF"×S¡ÊM £µ1£€º,W°Þk•ß`šñ-õ¤Ü´Aᓉ —ºU‚žd8±¦¡ºo¿!âAЈˆE8,õ‘%ÆR¬¼‘y$»Ú‘‚mÇÌTÛ܃5æ´¡³sgü\¾oõN¶Ã*%3û+·™33å›/º3òV«®ÅQË!rĽ=t×XIb$GÑLA8ô&v›IóÑàÝÿÀ”‰‘Ü=&–B¤Õ·eòÇÁ0QJßû›µsÕö¤Riçš5ååcFiÆ6EÀŽã€ î€ðùá7ºu˜TdLè½²Hë<æ8¶ÓŽgªlöûãÑapŠZ-å ]`r/•}H©ßÄqLa¶”-5~µ2LHzVhås^q¾sú»½/_Ï1þ¾Ôü™‹£‰½7tþø•DZãÛ•sB¡9ÔnJY’ Ž÷·aEô–ðÓ£/š‚‡*º· Sί~Gult{C nÅóƒf]°úIÆœ·ªÞ?m^î»÷^Ó3×ík¾„Y,XÞÌîÕjÊ’f~@¦9’<Ùµg5O‘™p}Ka0ËO9>yT —Çé<𨶥Vï{9²˜–KJ[]ùýž^õùýõîð’-fœ—4ãœkø'Íê"R€iîg"ØÅ$Ï•²&ÆÁÉŽþK¸éPÄØAÝ "¹æÝã^ñ…%€ ­÷¯¿Ctrí•øøþÍï¿§éÕ×rƒtˆ?à$l>Ú樂¹¡»yüÄÍèycMã¢Ïç£T !cO Ù3ñîÄ{§‹ÙZüùêíÔ_aó®ñìÌf¾˜Ën§jk¦$áËo¹kñîÚ~{²?û8sò ãÔÔäé9OÀ7h·©;ð ?û’kÓ-’JtþiyP̓}Þm¼¢ÉɦSÞ}NÔõ‰¨SIÁ¹µç ?sp¼*8o&ÕQLÖŸâ¬ufÕj«0¨ucgžŸŸÓïo´‚}Šš†€,K³Î«Öˆj#nnàk£ss#d™‰Š6' x“— YÀ ;&=#rѳ«Ú"+³EÞ(-W…bÊßð(”—Àw¹VFÜïòõNÜçESˆšQR¯w*ƒWª¥ºw«‰Rîa†ÔJ¨r]µðPð¬Õy¹’·“Š÷;e˜’]Ï€Â`è4 4e#P¢FìåAlª¢¨îE³SûDHàFø{§à™Û̆hñœFÞ*¼ñ{裸›NZ(:»+Ñ`³©6€[!Èå|]iÅzáÙæ/œ‚4Œñ íQè¶LD¾E}qtlmK »lËÛ²º/;¿ºa) C Ù©ˆm€Tfõ’UD­¬WSpɽگ”˜K*^`•l‹4⸹ùVFÝÜ$0I– '¢‡D,cÝÚZšX:U …>$¼ƒ¥â\ð½n‘ ò' (ûDÅN@©ñ€LÔUÝ`ü†ÄÉ®D¤H«d9mKZ?Ch¯Zpt1;g/s܊岩nU æ:úïr‰–±\N*6‰ßçb:ݱÍ,~ª0êĪZȬDzC›ÔÎ 77~í¥¸pqo:ëˆA:Ñ?xSi/¾¼SOÇï"àôüU–¡>}ùûy óö²™Úñ³Y4lmÈ… tä˜ Å5™ƒ8ÿz è9=ñÌÝÆý÷­AÅÞ)Žéy¼ìÞÎ_E#{>'Ñ?}ùDÿŽÖ) ˆƒÙ·Ùìrcãá“ó>ë!Û88’ø#öÁ,õ$DÔzbŠ_…¯aCö{4µEû t•òFG8ÃáfØ‘êí`° f†ÇBb­Ñöϼ·ì—Ro﬿àŸ8ÏÂ¥6tw37Í¡°^( à<ŸƒŸù¿ŸƒÆyD/qŽÄŒÐ Ø’›W:ƒ +hø†G2嗢Ђ;³‘áææ•Þ¶{U†ä)‚´uÑ'óä8™ˆY&€ þ)÷{xeLKùFÔºÚj¹'Ã_èLy¥@ bV]#½¶IÅwU•]^žEj²à MÓYHþ f£9$¢I"&4·ø{>Ç¿èéõ¬£ôZ>žp^Þ†˜X° ¼ž*yϘ ìžhZˆ6u‘3 ŠApl/‰9޲#A±cäB|ûÃÞ¾¹¿í <˜rB©Xc2Ö¸¯†À!y”y‰¨^SlBèPZ©”¸¸Á)cM*ÞÕäH”ü ›BƒÊªàÒ<“fg Xa@Ì€IŠÔ?³¡ø¢Jr@ñ’cœÊø, <µÑ‡^<§49Å5,št8 ±`|~¡JZl¬þTu;‹…XU`Ÿ[…(£è²=å…¯.‰ÌµLçôþòúêùå5,@¿@¤dîàÓ›`BÞ ~=¥·¶ZƒqÐU ñ†l¡G  G X¦A€š— G=LkÕyS郘æ)j¤ž—=RçÖgιf²x* "‡erñ²8ŒâëÛé½Ì‘Ï%ì„ÊœeÇ›«™HN3ñßÌqPH‹á£ÇnÃŒVé| †D//Ä›-ஹÊCq"ÔVÌ盪Z¬¤Bb¾Á¿Æòëd1¡ä€«ŒgUz•ˆeb5aYêÂÊh ³*~œ¯cè ?,HÝÚašÜñ(›,IéÓ‹Ðxa«eõQŽÒwbJ“\vŽ3ƒæ ±Â}3%£ÏÝ”c¾Í·-Ö™«ª* ÀZQ7^Æé6ówƒ©  6›¨"¶†Ĩc¿~-^ŒK}ÓÅ’Î üêòzt$RãüÓ }qy¸Y/Æ§Ó èìtÓWI \ª儞¢SZǽW Ò8¢5œœÉïWEµ¾w†¥>§g·]ñ Û#´L«‰7‚Œcžâ!ðð ›‚¦îÕäî­ZÄ9$”  ”+ILÓn6“0üI (:BŽ€ÄkC„@B°m$I.³ÛŠ"Z…I1 Fw3.5,)È´@Sž]xq°•Œõ¨ó rý ÈKŽObb7zĹAt ¡pé+âì:ÅN„áx9nðø±ñ¢+xXITâ1w^{ʈÂ("› EéÁ‹8ÿEø¹xr$LÐäqG‚ê÷ø–Èu*TÇÑÃŒ6õ€Ãã.ˆ¤sÓ=dœ½a[T«ÐmÿlÅOFOø*~4jÏÜß)Ì_Sšs\ø9"Z$/æ,‰üCÙXÙGÚéà´Ù}Û‘Yôrlï}´‰˜¤Àç¦þaHŽú––HÔ§tè>ìÆÚÍ[ŒoÝV°;áƒOýCH#ØZ\ÁÀ"ªaºÃ?pcmáa)8çüóT™ýÑÙ4½ë6wÈ›ËËE¸¯.zte ØJÙ,&¬¸I÷Êl憞Ì>³J·HŸ°Q·ÝK¹F*ð ÚõÒÒÈä ~ú9.¹WdÉ«jÈâÎ%âÎ/ñ” ï°ŸØ"e%rï8Bï ÐŽÐ\m™5,ºùÁJ½~à ví÷ƒµ/Ä<:ÿ_—YÿPW/¬‹©•TÒL©ÿp5õ¬¿×nÑŒ›–@¿¼ ‰ÏzmG›P±Ñ¿ðÁ…$ú }ë÷,ƒ®f̱{1`ùÕÌ :qð­­̆Ý+”±XK£¢#!(ȹåÒPc´ScÇi[+= í"!îg©×¸µ‚Ôº‚±ÍÁÛÄxD<ê·3®;;éBP‚„£´¼·c"DŠ ¬×¦¶]N ßøÞÁ/®~z‚çI¡À-JíðFxéf} PäÍ—5b9¹ªÚ†â@@£®ŒÉWÅaÎ5l^Ö0Æ¡ÐèœÈ-CCºþ Œe“ˆàÚ"€k£ ƒ÷U=¯6s+–™âÒ”ö=;ºq/ýpN/'wj{R ­!ßÖ©¦xÔ¾aÜ8º‰wòžáiMu ûˆ”`]„.ÏS^ÌYžæÍŽúË<&ŽÒB¤†Vˆ7¡»-{êå¸RÙ‹{â¹?Wœúðx‹–)µ mßù˜5§¶îz§ ^ËmPºWÜ® È• ¬Z¢5–õŽ5TÄ[X 䤊™B(àYì(#š·†Ë& ÷VêÛ\2IÛ RÒÏiÁ†M ŽÀ•»‰¿7²9ÔÇ1wµaMhk¿3z®r¼1¸ð^±!uGΗâ }.¾s“a^ºÊ ]éQÒøòtŸ¼ËK\>ÝËÃÓ˜{Xê9jxÈÐ]¯\)º ¼›€ÚFæÁ… z‹G×Á_Ë gŒ6ûðÙ:]˜zOK÷C@õ¨ª[CqŠÕiÚ¦ðmS*ߨÆé2ÄË`öôŸÖ ¢Á`EÑsÝ´ÐlÝ7§þèÚÞº‰К-Z]ZÃò(&…Flt¢Ê”ÔŽ‘,âÒÃ}ÒÍ,rïŠîå2wx ÷,,mŽHÚÚP¯F‹ŒN–‡)Í™ÆäîÌϰ)Ù¬³Á >ã¾Çd¾Q×]óÛûðóÐ#±bvíŒþUQˆâ²DãææÝìbæ3ˆâ9Æ´!›=Ù‹Ó”¦ÃEÝgØx¨Vü+Ûí=±®ðç5Y’ííPͱÞD—ä¸!8ÚN"C&|ÓŽ´N"ðè܈)_oYã•ö=”Ð9wVðn7ßç›õÃú(n‹m뎮ór¡@²kƒ½•ú–2–QxÃcd/”NǺÄ‹»q¿;ó«ñ|+Hð·Ó¼wæ‚OxÊ0›’ˆáÞ?—Ø3lôþ¿äGx4§¼Àw_Œltmï÷ϵ*gûKöîŸ*ÞÕjoò5&£rËÍMwÈ^ÊOܵ3cÿS‚º2|C×e¢ws3ŸÓðåYnìÐÕKdþ+ʾàÚG®þ'ݵa¢f/Xa÷”K‰¶¤'ι¶³Écäøï)w3Ÿ>½½§Ê8p·î<‰%<8NêÏí÷˜ºãøòÒ;ßÕ"øòPK›t‹Ivà'±Ðinvoke/platform.pyUT vVMXØWMXux öXÛrÛÈ}çWôÊ+yó°¥”k#SÒšU^R©Ò:.4†"ƒÂ Dq¿~OÏ À‹ä¤–lâ2ÓÓ—Óݧyrr2¸-„Y©¦<ÕµLòUžP¢RIEþ,5e²‘Ñ`°ÈrMüg4©ME¥JÛB’Q$–Ú4"1$6bK›LÚ¨¶HIìÝäZÒRRš»Uyµj'§«Bm(¯Œlš¶6¹ªt486ƒU£Jh€7/¦È—”—µjL÷¤•XËfàŸjYÈÄôw[=¼£ÅìjvŸ%°üB‹Ln‡¤¤mY™bKug¶XWJ›<‰¢Èµ&/ºs3¡ãU^ÈJ…p€0f; &Ó«ÙÜ>P€3£N}ø@ÃM^ýô~8²ÆÞË¥~NcŸ¸ø›m ­ýÛ¹iÚ„R·:Ã³àˆ´(BºM¦‹ë»–ÛF®ŽEDXv ëÓåôêóuHñx6»»Â÷ü·ËÏŸã»ëñb -ÔZ%€Êþm5Ù‡”¹Ò,FR¹¢Úlcÿ!ƒ‘Ë.àï+i·U;8*_k-ÛT¹w¸MóRVÚå†ÝwÑÀåM¥4þ\ÒãcPµeœ¨B‡ÄW ð3z|$³Q§Ò4•:iò%’n_ˆ5Š ÃJ´…aàõÒ ëç³Þÿ÷#Tm%–.Í׎tƒ:ü¤‘{)ÝV¢ÌÆ_t` Ó†5AŽÄ;7°P΄.Øã#F{k¬€>‹ÅëïUEXåë¶a­~±ëœC(øDγŸÏF!ödÜÁ’‘Eü½`ÌÛÜX3á%J 5ŸüÞg¨>4íÍ0Ä® gQ´²ËÜFbC‚¬Úü¸Sx‰¼„wP°x+ŸÎ5cª*@™ÿïŒ}69fdñ ×ë|]A×HT<íŒ ­kݶUi møéÓÐK#å‘s¢"YÂZ þ’鈖íj…2Äæ*1«\ŠÙmTD_P¸ùÕø+‹!Ö!=ªEòà €‹³NÝ1l¢Åd6þœÿ«ÈÆ/g¦ZADªZìô %ÕBT ™z1N-{cšíÔ]\þEVe[h;‰>é¢ ! õÞÑ}Ūw–/ùžT]åL…d ÃÞžélq}R‰²ÚºíkÛ[í üâq½l ×F=Tõ=yõŒ`ÉhgLµWtçWgìhÏx‡è] u^¿×­!0Ã8×Y·ÜB‘»Æ³hr…W"/ZnxBK_/ÞÑ_hçE`– B©ª'¹­…I2™†¤Û$#aûW}¬§”í'^¹^ø–¼L<ó Ñ­±nâdï[”;=Tða–=Ðbñ¥æÃj7õÒö`†(c- dÜ6)­JiP&5º÷¨d¹pˆs-µ/A òØïZ |© &:¬&wfºœ_zIääg¬‰GD‚ìJÑYv“Ùeò%‘µp±¡QMH `ëÚ]Nfþâ²ÓßÞv€¯…ÖûåŒ3¨+[‡ÅÑíI l ñl:Ÿ}¾Žçã»ëëiüñþææú.žLofAß1÷‘EªcÀðkÿ?Á0ÝÌ!}ØuÅQøêý¸m´jn| ‡~妷QwÝúÕ2Ý8¢3â¶µ¸Šg÷‹ÛûEì„c^9@éeŽÛÿ€¨Q‡O'ý;¶|¾§b åöËÑ÷±wFFó Dñš¨CCt—¯3C§GO?Ë•¡¿ÒùÁæí«Í•1à}Ç»ª>ؼ—b/¡åÒ¸'{«Zõ`ð£)„4ÉO6²‰]¯‘)W)Ê#šq£šDzž×3¼ÇG·t ¢ÚXä|v'ÕSÀr¿"wÌã¾Ê_N—(ò)×b#Kýw´õ~Ž0~pxªêC*³#ãÝ„â&œCø³åÒñ†›T‘ƸO0|»¾lЮ֤~¤GØ,ñøéõÛ}px.Vì³âhÉîL}pæKÏ/¯î.'Óð@ߎbqºA€b¾Â› ¯êÖÄGñ[ðôøèÞ1½VèßøAOXiÜñP%QD@“ÐQÙ‡ï¢FˆKrR.hÂßä&µü7†Y ¸}žù“Ó~œ¿hZéȹ? –ÇmLˆ%à š×ôø1½ì†Ã‡ÈZž8!@û™úǧe–¶ÒHXÑÚЉÓ`ú´cÊèå1¸ª% m)¿ F‘7ë”к-AOéD @ú„zC;Óí”ÊÖòig'9h&¼´ýsVBgìå@FëRÑóðx2c,7rÝÂÒMéUñ“Çnž? ó^Ò³‹òãÕ7°FÖI¾6‘‚¿ÅøcÞg}¹¯à«;ù[H_ý¿³èì\*UVŒe`öêëÙ7û3ŒÓÜx¹E·²ø}¼ÿle³ípvb-%e`í¨i[' —Û.<yF;P^ìlœ¬˜ 0«Ý hÑâB÷íeyÂf"m\z1n<­ (Ë[ñpÑñ\ݮ׎‡’ÏN:·ZÚ_&ÞHªÿ#§0<òä¼ä_j¼;ùl>ð8uÜTZ7j ƒQˆ ­hÀyœ ÿG¨°¾ƒá`˜êì@NÆ®¬ÝLfÓ»ëË«N áhl þPK£t‹IØ0Æ qTinvoke/program.pyUT VMXØWMXux öµ\Û’Û8’}÷W åˆ–ä–Ø®Ý·êÑÌz}™qŒÛvø2ýà­ ’8¦H /U¥íð¿ožÄ¤ÊÕëqu‡«D‰D"‘y2‘Ô¶*b¹Ü¶M[©åRd‡cY5¢-²M™ªež5ª’y=Ç*+êWlš¬,<0³¢>ªMc?–µý«>¹?uÛÜTòøàÁ³%תHËÊNUg·¶aS޹j”m²Ÿ]s±Ív¶ñ)2My)SåH¾ÈrEó7êðŠŸ›NGYÕ¾Ó[þ43¿‰Øœ‰'Õ®=¨¢1cÔ­Ú´çö¹ùìš7êÔ¶Ãä Ÿ…º…\Túü6#ªOË1eHˆ¬©U¾5|%‰¨•­K½~ü\Vj{¹ªT[ƒsYÏå|²:­˜})lx&óŸodþ¹ÙWe»Û‹rK3fµ£bO†¤#sJ"á¦jKk¥ã%«]=;S?»í„Ÿw$Óª@Ùæ «ÄjäJè­£ÃHÒ’"'¹&ƒt:-®Iª†xd~#O4”6·7{üCGòHebi¶Ýª õÚ­JóôÉ=À%?‰žâ‡Ur1Û£;žMg½NŸ³"]¬Ë2ï7™µ/^Ð&©~ó^åÇÅè-léÐzn¦!¹‹ iO–ʆ´۶˰P>ò´†ƒ¤)U•Œf$œ¢ü—Œ(wXüúêøPŽgbœŽ¿ÃòžóY×'_”mslbüÛ8¾©È’ϧÍwÙÃñ¦Rl-p8È4lĆø›yW›} a«ï"l".´•§³@út =ªÅZmqúª¶(È*|ó´ëÂ"¶C‹Ð¬¼k‹&;¹s[iYB†°tdü¾™ Ì&öCL”Gm¿ªö¬¸ÞïËm˜èŒU5gKŽ6Ar#Af¤«ßÈd–› Ãjœ…¼–y« m´O“é¸z°ø|C¦ö›¥ iåßCï^q!¯e–óÑëo•ù|ð{üü~$;**´óÐ'½¦öÜœoúµªjRC¬ãßc¬Àf’›Ê’ºó²ÈYü7߃íßh†Ðv£d ß’æ0äíˆ7ïØ‰«—@ïþ(.Á˜y¥p’Åü>qD>ìT–ƒ%dšÒh²kÀmlPÀ­PŒØ¤Àòªj$†ˆ ur„,ðòàÓ@o*æ xÒ²<²ˆô<$ÀªÝÀ[rYm°‚$`PÌÕ:¾ZñÖ夥Y¡˜ŒÖÝs"éš5‡Ó{1U5„BøÈPhm œˆu8’ë%ÍŠbÀÑZ”´ú£ZgMEPMðÜHžij·r=ž~;ž³X*¾9ïÄÞóÚN¸gX&‚´o=iE9'½ißX>ËjgÏ“gzc¾•ïª,Ù—Tç%÷t/‹AêIp¼RÐÒ“Žy ê[Z¶—y#kZ}åˆ?oXY84‘Ý./×2'ßI1 -Gµn×:H ÷ÑП‹ò¦@So2'ìxª'ĪÏi¬ >…qBPpͲÐàADâå–æ†´ÈÀ¾Û:±©¶ñûMFŽm­öòZi{æM~|(°ÿ Å‚`É6žôAqü!ÒTI|ëœÐÊ8ÿc‰°?¢$Í®ÛÍ‹ºÔØør5ŸÃ«é±ÁÃM Šmà[ðc2d`N1)¸ù`€gq@BÖ3ÅÒÖ~v¦e¹g ¸Ì¶øcOø`­T‘"ÓbÜí0׉ø ´t–åˆ(Zo‚Θ5eDŒÂi²ô‹3NiËr‰àž €ZÒ¬>æòÃ4’¬ˆ7˜DR ý²jWª†Û7 0ªU¤CNBlԲܒx¼4¬ˆÁ‘Â6ÜužÐÎÈÆ¬‚SQÌ«9ižÝ;Ý+y7ò˜52çÃoA)g–ÙûŠ"„›²J;êd2]¤¥×DÑïì*!ÌD;ù‚ÔLÝJ$F :Ô†Ü2…qêc ;Ó®µ¹ÉsZ­¶%ùˆŠŽ4Š%îÀLÆ Ó<,Imuû²ÔÏ5’c¼ç÷")Ý‘–¦f¤C2]“ÄDÖ=¶åà­I¡ÉRr6—``nÌ´ÃÊ_³köÃ0td?!d á8rŽPKâhÛæZî§²µ‚ÅßՠƶHL‰­'£ÓÖ`æ,É—j((Ò’–;Çž²HœÆûYòºhÇWÚJæ’?Óq¨ÍÂ#júDmWdúÉ×c–ˆXoS8Ýš< ³ÚWпö•ÀÛ_:Œó`‘a°C9A‚¸³‘Kºùþ½Œ=û[6³†1¯u¨~?ŽTŸD q¡o5ú<BÏײ¶ "ãîäÄ>§µ Ù=<çi©[V•@4vrTf×îbeõòõ?ÞüýùFÃvagé­ $iA ¼š}L&@(‚ðöaLÀ»û…÷«q¾51Íc Ý8·Â¢PˆB}§ÑG_ï2*ë% ï< îÊ)* ‹>Ò s;Ïåöóø$áðSÍÐ zlvn¬w÷¿Èˆ!–=9ô®ç¾+ð$Ðl­µ€?3ž±Á?†‘#(Õû nž™&¨ÙærWsП´ŽÈNü#ã/¹F•I¹Úˆ§‘[Ú4S d̉ì/\ûpxFúò‚­EÅá.)P ñH"ãæà"¥eFH掴ضÇl‡iéÀ§J Ìd_;Š=“œ­íB5”yj£pí2[rKûLU²ÚìObr³Ï6{Ÿ)6yK y®“i”ÏñvÀp±dŒS4âM‹Pgï­|¨Ç—Š¡Ÿ32ÀíQôöNãÖ„$èþú͇ç—:é´’Éœl,Þ”- F’ÆT:ÎÈp!Ë9–› Ö_´n¤¹iLÛF¸p™ïýÄij߿x‰n!©?)ë„Ù‰m0Ö}ÆWDùA:ɱ9ŽtðÝ£‘-?;œSé?Ô˜njÎÒãkœ?:nè *Èó`¿Ü Â8)únƒRdâŸÆ¶ MÃé×Ç«10&.Ç—àu&Æ<’>ñï/¡òá–ê9a­¼BUŽÓBnÒ…û+ŽdµXîI¯†˜µAÚÛ¤K¨ú¬+c‡åQ6û…ß͇N< HZt܃ï×Ën|Òw.œ¯AÀ|­œMç ¤3ž@Û0%p!Ë5y¹Ë63Æ5-zèÞ‡,xÞ*>XoÜ=ž; €œˆ_µý±¡Ã̤¥)ÆŠsMEð…`7ß÷ëO ÙØÿ¢¬짖̘.iPÌòì‹Ü#Ë fô7àÕ r â—KDRÐÇÕjj‚ùlWèK_ÒIsÀ d&AÈ—Øh+µ¾1òÀ‚Ñrv;$9`èó+Â& 8^uÐ[’¨Ëx%Zìä¥èÿCY#ð‘8 U3DC&@Õ Ëb6ŠzÆ´ :3õÈ1:4¢É ×§­fàŒC©zϦšš×„A±±=Z\ùJÈi[e ¹U¥‘æå‡Amº±«‡ 7&Ði¿Õ(ÞÄ7™XrÒ­Òÿúí›tÌßüOFïÔFe×Éű$ñÒšçõçìxdîvüRüþø‡êË(Aùl&j3øPèz …éaâšK}ØYM”¾m¡ãÛl`~ …ý$üK‡Ù‡k>l s"Q¼^ËÍg®Ñ¡mÇ%»V®Ò‘´‰×WxPöˆ•´Cà¬ñƒ,Vm³Ÿɪ¯‹\–†^À' èrÝÈ®¦wÓŒwEÃ=•è {¹ûτڬt1w…þñ 2i&t$un Q5A úŸA"©3ÀvSödÐ7"÷YH~PÊÈoÚ*íuUyü}ˆâûкsнFk¢&èËJŸš9@/MÞ‹NœU’$ÆA±ÕEæÃ¦Qïÿ]Ö¥¬Ò—Ðèª=vvÅñv1:@liQ$æ}{jöˆ\uþ¦l›÷*åV¼{þöUp¢­‹w§ˆÌb8þÔù`²˜æž)¿îùçá~:.Žœ˜±bªy³ÆÝdp;á<‘•êcn—¾¦mÚeöáûzO¢GOØQEóʳ© ÃÂüÔ(lòõJ4ïç&Y§%®”PÍ´´ÌÓ! Áµ«v2°0œ3,¯Úñ°„µ!ûÚ’y&ÝÕÕW˜ž@¿¹"£Š UädJsãòÇY@´RÿjÉÝÔÎ÷™(úÙóÿþø×àº8ÂÏ<|ÈÆåŸ“H†ÚºÛɬá‘žË šÍlâ{y#\vÛTc%ñTôd4àu½°é:‰;T°¶%\Ò# &\ø_nMx`ì&RØ ™Wä½Om_>d2×÷Ö^1I_`ÓÜŽÊL°ËÕ:ì83 ¯Ñt¨œ°ÙmDÐÏGõ—áìuÙgîòšŒG:Ba4e‚yˆ…u8ÍêÏÃ,=+JÑ+_ MÔ9^/…™øgK™7o–Š´C‹KfÄ„H’'K³TðíœìSŸésogÐ77b¸ÀÀ GrKà‘©Óp£MÔ!qj =#3 ’ž“"UšP«6z]žõ¯‡Å²–žs–Ë/þDõn\8(õÎaÀ¨ÎIÀOpz±ô¥—FçàÇÅ­ ±l✫Ù†xë]ýìÉþ̧lÐÆsK8í^ÒÒƒ’y{Š}ŸÆé=`N­8Íe„s2œ]ïmÀ4<×<çà6–*دYg§îa¨öfØJikU>ÎÎZ[0~ÙUR½š"Ü‹ÄnX_I5j%Œ-æÕü CPÖ—búŸQz¢ÚWÄ@\öÇôu‰~óìÍ¥Ø*j']•¹HÛök+Qá¢Ý'i¬½}'ƒ¢ k7}XùÐ9Õǘ|®É\JR0ÙˆñᅩI‡~p0 Û0p¤dD7H̨Ô8Ÿ¯»R®tð£úS£;FgäØc"¨kÇ.½¶æ~ˆÛ6qn¦Gš<ürc_€éx¯HB„›sîYˆ›±[Ñ‚â˜F;ÒñÔº0“%"‚O.yP·ëªÄÍš¾ä291Öé¶¢¸W†É&smA_¥ô5zX™8s&ÚáèŠ ¾Çƨ¢ ¨|oñRŽ0I)ãh"pá ¦¯tp‚üœÍíô¡(µ,YDÖ¦ýwZ:)ÞMé™8õíœ .5G‘,—eª¾´çB…¨Þɾã:o» ÖmâãŽËŸ¿±r»åÛÒ ^ż)ç6[çÊ!“A’Å™KÔáœÔÓÙŠ`¦swÎÀ?wgªBêÑ—oíÛƒ,æˆ9úç"é˜7þ†·ÊÜ}庒ok†%¾ZÈšõ>Ǻëã™ìï¼Dò÷ÀAùÔ¤žêˆÖH'l~Ôóuþ ¸Ú:ÁeQ18y²ð?=¾:»Ž¯½,¦’l'sÈ ›c‚V¼¤i"˜ó§$dƒˆ3;ç¸ëÀ™»Åí‘én‹’Ÿe¼s« v·ßþ hõ*Õè„ä ‹çØ ƒG¥‹¸ƒº21ºž åcäÜý}<KzáåÕÍÏ fIíO-~ZxÀÓ¥cö%zyg¹^àŸÀ7Åy Îvèœúˆ°¹øýÂÛÇá,. "uuAÓÞŒV:´ðs˺Ýê¢~µäB|šs¬t1/M}E¨"aPòÚ6¼Ö £»¥6œ[êÎø'_xúgÐ÷ý죮@>‚,å Á>šÎ=)éÓ<‹¦ Ì©¡7ê=áøÜç^ö›GÝdK˜ K{( Žéœ4Î,›–¢ Ò“¯(ܰèúá’ßáNÂùŽcý"ãRD”.é<¹’\µ)‹“Ѻ .«Œq¢¨X®’a¿n –ñëæ~ö®•¶lÇÖ:L›s´),cxŒôÄÄñ: wC›¥…9g3j1¹éËã¥9!ú>ÞFždaÈš ƒŒÚò_\^ùA&ùe™³˜Bƒm<9yž¶àç)=¹€oï,š¨D:DŸø3±ÃÃ#ÝM|ݱá&oÜ©õÎ]•¿”@™4ê™rÄFVM”çÂ{9Úó­‹~à„Ç/ø_/NpgËðÝ1ùÞE4zº CžoâHNqÒü\¹vÈ|E)j|â¾úêkEÌYŠŸ‡HÏùìù‹'_}X>}óêÕó§^¾y½|ýä×ç1„7µaé¿75z*‹qÃ/=ñ™í¼N–jÝóÙAÿͪÎ]mÔ~.3¹èmîÐAŠÍÕ¶Aþ¨™®Ã@ZÒ"jÛó£Ï×± Û}L½Õ1Ö…u3SÛíÃA×-6w‚«`ocl*²WyÔ”ÖÔD¾cØLvDàt°~7µÔ±N¶,LŒvmTl0ºLhƒÔ›¬ê:ˆî¡Ÿù¼k»9Iǹý! ê ”yå*+®ûIÛóuxÅÚïó¸öi|Ԥꗫd3Ö/<(YgŠsü iɨ; f0™}'WßoÎä´h˜Ž/”JƒZŒž ý~š÷øoàšÛÎþÙ%|¹+oÚ'Àv4·1rð[ZnL¶`aß¡Jvª¡Ç]=Ò´ýȽ2fÙᨋŽâ¤¾|Ò/ÓjÕc»ŠÌÊ, õzé2 ˜ô”vÔˆ&3ØêJp»Y/[åÑè>í™]~ˆÎp/ížGæë¡x§ØÀÜdõÞ}ã^ˆAÄZ5ÀD[“n‹Mú ýÊyC]GøxL{2X‡‚6N‡:ø•¬Âo†þÄ£úW ÃwÃò~Ú§04û¨ ÐùUZæÉ›>:v;9\cˆ¬»ý{ùóàÈc䎑¢©¿• ±f³Ÿé4˜Ì{ ؾ~À†ŽQ¹½­w!é=)WVmZSa`SÒ¨vß—7¡œ¸¬ÉQ‰—n`¥Ëh»Ü{àåùÂæ‡Ñ€(úpÒ±Í(àì-‹##³ ‹þäíÎQ? 3‚«9÷íZ¿†i÷Ä=IQ7žÎŒ‹Í3ˆuˆÎ'3ÑŲ±·¦1jBªPÓìö^¹žÄ¦ÎráeóäŸeVLÌÐ^U Ø÷Fö‚| ÷¨»Ñžz¸D}ÙŒc_G]æA%Y.i²å²¿;.ì•䯲DvèÓãxFÞÈßbU¤c«™ÚÀ½Þÿ' 5Gï½uôòŠ{Ñ“ø{]âQÖ‚Òƒ"a¸=3†çN@ÒÈõš#§ÃÒ@,S‚$šJT^ýQ'ýµ¨ý+ndœzšÕoØü¤!ƒ{ªšÍ98óTæ›1µaHàëè‹¥%=»'.¯‚k'J¸ûbPB@šor§ªÞf˜—m3龌à o'¹*&·HcòºnùµÙŽI%7&Ž@ì—çõUnLÕiLùùöà+:;ìѳ¨¿¢ PÆzôäýñ›~<áY~~ œ¼íç$Ð5c ‰õgÞbÌØ?ß›}[0H7¬%ø=±“ »]&íZ;KÔ/D°—s}d«;\[ˆ±±pýð1Ø‹~PÈ‚|ê¸x$ÆbÜïÓÝBÝ/Ž*‡eàD ¤Ý©¿E]ˆ—ï¹êc#Ä 'gê»±ûÜŒM;_\^ݳ&´ZdŽ™¦Sš‹GÞ§æÖs˜TÆxw!ФÿPK¸t‹I‚äÿÍ9þµinvoke/runners.pyUT «VMXØWMXux öÍ}ësבïwýc²Ê”ì<©ËÕ¥-9æ][R…ôj³ŽŠäDÀ 23 …¸¼ûí_wŸç (ÙÙËJ,˜éóê÷ëg“Ç“lQ-‹òæ4Ûµ«ÉñÉ£GÅf[Õm¶®ùÚØ¿ªÆþÖ´õnÑ>ZÕÕ&kvóm]-LÓdúí›jkÊqöæâÍK÷ÂÞ½ÛÞÖ&Ç€îƒbc=:Î.ôÑjc²í:oWU½™4[³(VÅ‚^£Wš,§ç«m¶6wfMÒÇfŸ-ò2››lS-Þ›eFï´Ö4m3}ÔÖûÓGýè`ÛvÿÈ|X˜m«ã½¬ëª–Gè»ì,{U•¦óÚjQ¶ëƒ/ò·‡^mM½)h뽬ßÛ×eS§òtQ•nW‡üô¥ù@[ÒšåËE;ξɋõ®6ã슷õ¥}mœ½ÍÛÅ­©y¤ñ£‘µC}{ñêÅë·—clÁuSü“à-nó:§qêëùnµ2µYŽ3Œ°¿¦×¯õÇÙ|O}ÝVü‰f×k;ÄmÞ\¯Šµ)«qV4yÛîÇ™›ç·y¹\$™¾]ý)—Um4ŇG-Ö9¡Ø_veiêa5ÿ;mÂHvðèèˆÿ}“×m‘¯×ûI>' ¥Ùj׆þ³ÙÐ0“šÞ¥¡²ó7ÓGüÆÕmÑd˜~)«6Û5ù|mhYYÑ6f½ÊèÅl³kZ ¡:?Œ½ ¹­ÍÆ”- æ ­Ümæ¦ÎªU¶1ímµlè…Åm–7Ù¬iin³q6»Ï‹vÆ@gµiwuIÄgfÓìZnî°ø##䨍Ì|ÈñÁ8kŒÚöïêV·4Àùl6ýä:›F›‚m½^ÜîÊ÷|°„fOŸ¯ÉhþL׳é×i¦'€ŸÓ-¡ÚÆŽá‡ÀO¼B¦},´;:brÙlŠsÎwkâQJ4ØabKwÅÒÄðÌ©èÕŠö°¶ƒîò5mÎ’FqȆÍôfJ¨Í¦jͤª : ³ŒÀͦ‚³lSÜܶÙ}^¶ñœí‘f·Õ$ÃCdù¼Úµ¤ÛªiË|ctê„÷Ítô(zd:ŠšÓx‡íÍn " ¬ÑOÏn]“=~ t~ü˜—že˜ÝJú~UÜdwùzG¯ÃZÆ‚Tº)³Ð÷رivÞ*)„?¤Ín3 oˆ4èÄB &'!špºÂ:%äšeïÍþ¾ª—Y^ßì@ÍÌâ¶š)±Õå,´:/×s×é`&;¶?ÅŠù±“Ìr\·3:û¼áíJ  ¦¨4SÄOH$ǧ‰©™Î~ðë®Ì,!PA’SW :ÏfNÌN_Ò(@Äâ¦ÌÁjA7Bp`(|rÁËÑ„÷ÛbŽJcÒ^¬HX&Ü«™f— ë‹×t°$2Ê¢Ù4ô½A„ÁÑRß—Õ}v+ë6$Âè¨@X¤afEJGµM«3¾ÆcÍ-mÑY–¬o8òeÄ}šÛìrK:Çì¾Ú­—YUßÐìˆâDuO,MRÑÖŒÉö X$0fš]¬ê²*Äj*Ádbĉh7!Ã@Èá.'H“sü3€´¬$•pô½/ ²ÓavõúÅëSfC<«Îþ™³1õ ^Ø„Ô8;‚‡Û€ÄªÝÍ-ý“·¼PV{¼ßVUxÄÙ·t$÷"¾³!iU›ü¨t„sácØÒ$èLja†„Š4ˆ!¬Á—Mr>]!Ã__óį¯Ó‚e¼(Zñ”¨‹žæýáí¥ãøBv_ø-c7 ÚÞµ4†6oɼ]d„&˜K´ >â–Ç#[ ŒC$ÿ—ðÈLVyÓòƒé²cš,9ø2àí­aðP6W„‹ó|ñ>£ÂÂIU"=ƒˆÃL#Ž“Œ‡ÇÌòš…Ç5T5àŒÁucb°.á!ÓK>:ÕCa€%Y }‡ô‘fKHÊÈ}Q¬×¬#x´c„&Ä€¡¯Ë ÄŒ;3ä! ÍþøÎkô¤S$X“zO˹i¨/?˜ÅŽ8Ø*¿1›•ç°ÂUz  ÿbbÌ!«ï–çDü2.±úÝrteÂN,)SWD”MjößV(YÆÏ|¤˜Ó´IIS»…™'A"mRêNp1…Æ»Éê•Qåƒ?ªLSu¦ %¥Ì˜Mž j+ U\Ž3Ó.FSBó¼1}ÒŒ /h‰’£Œ‘µ4Àˆ57ë꾫Å—°‡,ø58®|$\Ÿ¶ÿU~TRáÊ«ó¢Ìë=Þ$Lf/dÚ§´Àúê„Ör;ëQ'çUµfŠ1ÀÒ×Lð8À¢Ü›ÓYƒ-NAU€•Ò4¶¿'E–Ñ:uYK·TH·FõàJY•ÿ45q)ÒìwM´"¦âh5‡q?lÀln-÷(iÉ\#Ó‡M«æ`žØQFzRÖ°­¦µ#B”Ö‰ýùê»(8ÓÄÄœeb…5Е`‡wàÝ“„T `Ø%RßãnU’t^»,6I‚5Ù‰³ihÆÎ‚y¸=ÚâA~Çë§=®2Ò†`¶ÁŽ# ëWvÍNöÃ[AMƒ© î·cH»EÇHÖ·%6kê;à $“5°ºx|K£t˜Xu/l§$ȼ,ÄrUFBÒßÒ÷ÜÜæwEņé¢ÚîS¤f‰èœ8HG’/Lô«©kË1ǺZ³É^ Ò'§¨Kæz$f˜õÙ€à ˆ aÒ”0}ÀJž $]ï­HN-"Ñ7ÆÍ$&$Àäóq–pD dNbÝC¹ªw&„€ouÜ1¯:L·€½Ÿ DÌÑ´è„ ÒÝÍ©÷ì´z–LDi<ÀE{v4(´ŸJü—ƒÏ×÷ùž„M¾%a k‚¿®j£­Kö3+>- CÒÞä5¡9i¯¼yŒT¼ôOž¡îºls°àŠ6§fêa!e >Â'@Ëè€cih æÞÄéTŒÓ9Þ¹ÊÖ“¯¿»è!!¤/ÅüjotìÄ/)™†—EMÿ®÷÷‹ò®‚sѺ:SNÁ¼Œ¥¼`ò‰žˆE&1“ÔŽQ>$Ž5FE–Øg¢5#š;ßÊVÔaWE»Ë•klh…â Ù5ªå ¸3bWjÅnm³[Vî“!í ´–®YÎzi S|I™ŠÕt@óôªždATÃíÁ‘DJ²ÉE•yËÞLEíœbô\H»–jÇÄ÷Û–Ï‹uÑʱ–uB#´Mm&þj9˜žXøÛªi ¢¾Ç»¬²=ZWv.Ά1›‘:%ÁëíÖäÄc»þ”Ù êºÌœþ;VžFL›sãi•G¥Ê›L|6ë i¦U¯kBK¤ùÄ÷!ËôTÇL¡Gæ› N¼»Ò~"²G|Œ_‹` ªØµÕÄ%NðÀº#Ô§MÞ€ŸB3ªfvä„A†ûíÔ5¶¸sÞÞj8K³50ø*5ÜU厡Yo•óÃÍxᘞ#åëÁ•ƒOXõ½Îq&î)æÿ*žUT»Ÿ—#*}ÒŽÞªj¯óÛI7ìa¾ç»Âg»{°,hÃMyw˜…zÂÀ´( S/40’ Þp’ Õ} @E]•prEð†Å”ñ¹j¦úIgâ»í–ø_.³€ß…Þv ¯3|Ùâ¶XÇ 9"Ù1¸Ú3ÿp 5ã½³ŸoþzõíëWoί¾Ä+œÀ}B°ÁdOèé“M¾Ÿ›ÁÏ#l2»š6Õ QèØC¢ïéñÚ]N ”-|m§1ëQ¦¼0K¬øŒÌd]¼7ÖU(Ú$›Ú<—®¥A4Gzff`ÝÅÊÿ-“ Çû®°’]°VßΆÁQ@Å;™Z‘ɼ.Ѧ8bÔ]Œœñb.sÒÖ8 Êî±ÚŸ¼óñäÙv´3êXX•Ÿ! åîDŠò—o*Áò”Àü¶&NRU>m‹ÒÊ‹ƒûÆ ëìK§ü ~ÇÀ¡c°ÛücyÐJQÆÖ›Sq8F›ïUŒ(Q  ~‹‰eŒ÷nàÚBcWøÌ[ñ¢]€‰@ß)—¨vDÊ Ñî(cW2\§ ‹¥-if¦.Ù2Oò­ÙÕ´%Å"µ)/I<]V‹ÓÙ À¬æÄnªðgby±nl0%[íJa—Prhkúñ݃zÌ5ïËa¿o#±Í\4æÙÌa'í«vŒÑ!i$s¹(uíð<6`Τ¥Ô]t:¡yðÿ]“ß :ïbg‘êX¢Fd›D‡Iøï«\VÆM>Hü¡Î[q@}·dÙ%¤1ÒI9 ¡.nn¡®¬„Ã'Ãü¾£‡=Î^qb×y!w¥¥[«$Zåê¯Ê–Ѹ-ųyD¶m"¡1ŽTcme-QŠÉ¤"}ëYÏœÎiDç»ìóìx¥Í¦B 1rr@òaÈHm!9sè€$lŠ¥¼4wüöuvuõ×i¸áeà=Wëì¿EeçÔ’©ä‘̲}aÖdäNëè¦Cµ"šne—ŸÃjµü°ã•XíÔhàinBFÜ[±Ë™@ó&=&yQŠ@»ež¨Ô=T‘.1tõq ê޽¶éÂÖï Ó5ˆ¥uÆð_ƒwÁ\^z q”vÃtUð7¦DžMÆ¡@¢ßïÆ 6°(ïÂLŽ—m–U馸¯ %=5xo3{a†G{òå—?>}öå6?=ù™ÿx²9šJ’—=ºQ8â%2õ' N<ªŽ*†4!ÎOðÈì—/ñ˜Á»Æ®…p¤sâIp‘´‚'ÞöCê‘ú¹5‹t'{Ô·0qKç^µçd?¼+âgRÛ.ʘ¹4¤¡lŠò” IÙ­l’A¯¡‚žf?Ét~ÎzÇØúW‘0F€#'|Í‹8Ë~êÒ„¤ ^ œÓä ü Äa~=8µƒuË‹¾·±&œ’ìñ®ç ‘¸ôN@,ÑS?Çk¦Î&¯áÃÏ‘ð”ƒ1Gê¹æ"ætòšfÁ k•@“¤¢‡næì«Þ!Zù8ÒÏYœ:ÇÒ4;#¡å˜Ïn ™ž~ž½ c•Hf€<¿‘´9—ØæôÜÏx´3H‡X¿×@6¤z¢ÂÃ} 74 ® á·Ecåáô¡³'»¤ïè%›fpêYׯ9J~йÅiÀ:dØ9~~ÔýM“yʬÐ_w¢Jšÿ˜,Èâ]õyJb#|ù b#îùK;àüVxœý{A¦SµZyÐÄ,F?‡"µCãœ$JK7Ä”_Ñ<›âÃ9XôÿM3 6h”l<>¨g1ð³TX¥³Â)éíø‘VtÈßæ…øu|V$”IH…±Õؠϳj޼Yµ+B†În¨ë¤Le÷Nmµ³åL<@`uŠõ)ih¶ëji94Ýó´§E¦í3œ碳·ÕV¸ „Nê+âãŽþdBLFÑàrO'¿y‚|2É%ê™Ï%dX€É ¬mÕ´“ævGG}òaÕ¿ónˆýÝo³¯wÙűҵMÈ “eqš&i*Áò1`†û© æri¢1í}¸YÌߘ¥Læ´—p¬¿çïÜ2„ø˜±ð—õšâ^;þòwO³!(q[lMLvƒØ.2ÒÑÆÈ ºÓ¬wF?Ì·9LŠå=à˜¡qU¨sdþ:âT"‰ÙHì±±ª ÜLq‰±|ÌkMáìl‡í^Nx=.G¡Ñœñç[)ÔØŒ/ –3ݲÁ’GIIZöxŽà;q¦»ªXÒká.{EHð¶–¯Ö–€>ª{Êfc- ™ýç¤5AA£Í¶~w^PpŒñäì,Þðü©È¬"ÊHAÿì¬GêAüHí4n—U:_wÉÅOæi"±«Cý:áÈ®÷h ¯€÷ÆKaÝ™Å*~M”œ¿Â™Ý ¶‚SÐI šyº1'Í(ÍŒ§Ña€»7RCë ~ðñ4½0œ¸âàd(÷`dÖÅ–ùNè<âCRBºŒæ™'B{לÁ;øÔïâ¥Ô¤–Aæ·®x¼ÏzlªD%‹ØU°æ0!4õlä°ñÝ×Ónx¶ù°Àâ…¸ÄpÔE„xæðj‚pë¦:ksØd?I°Š3CüNˆÚ'j2ÍX%VŽ™e9pŠóJhùÍ 2àÚP‚I\(§Îûp)qe ;/E>V˜D¤æ²öE8UrUG³Óâ5¨“„›› 3>ˆ£Ûf #i}›š™ —ú ùT KuÔmŠ/3Èï—¸ÂY6ó"€þÁôÁ(Ü1-4MEæÑexaaiî‰LsDR\ÝB¬zºp+4ê&òÛ÷ÄLý[9½ÒyKf^©A¿·E¹Dú¬Úì ˜áßXRXh#«Hr}Ÿ/Àj!y9ê§²‡sNs¤§ôXâ¬B®s§Ù¡óÆVèà)aër(Äóh ÿŠlÜppÑJ8$ñçûÔž¶ç$¿Lu‡G«ÿV³#úï(üÔ~–B‘Ô_~”ãìϦUÄ<ç1Y9cÒÛׯúÄ%C7—¹"¹B„mÂnŠ¥xLjUnȲD¦ 77MZ¤| ¿B·CÆúì ·Ûl'C¿–P–n·Dlö…Ôús^ûc>G_¨âÒê@Ü5Qº¬Æ”7tÓ&e¼n’+FZÛø„'[iBD¶{[ße(>…47h£bku[R¡[ú¶B²mÐЭVU=BÔ±°½ç·jfc’A…¢,¥ç.å=o8Œ»àŒVÍE˜ùóÐ<ÜN‘eôrsíZ|$ÌîÅØ™¼'ëb^§ùÿ®ï",÷«Lç—i½œˆÝ'M_[ddÃÙN>™ù¼Þì‹g§Ç'ã¾þ2ݵ8?‘›LGÀˆZì£õVo] v*Ê“Î3=3^¶c¡kXƒÙõoªa;×ú+}Ä-²j:ÖNG± É…éF¶Íšôá%‘‚qÄ3•`qaM ác…ª–õÉl¿˜‚keG°Âü-»vR­&s.âPŠMåÝgAuG25QèÈÅaâ@YTšPXA½^L#xâOµÀoßr®8'Ò£^d¦µݬðsÔý2Ó÷ÓÕz×ÜQ2U•ÁóÌmJŸó±_Úæ ŽF©p‘ÌÉè­ÂùôrŠ1ñKåÖ¶3*ÚìT“G—A²l5é‹Ò3î²xž°$çXîr7ǵQùKv÷á@Y^Æű³û”Ϊ´e˜È"®C|õš*s„€5"Ü0‰ â9"q•ì>—,Tâ]=Õª6$Ô¸v™ø‘ë®$Jˆ­\á3Û¸sÒÇ>)ŽÌøÕZ,(zÑO×å¾! ;³+¢ªÒã¾Ç1¿®¾™ü‘äh³( ü-1íï¸Fì†!3Éa "'%¬[O —[ÍÇvÕ½¾4k +¸:¯*áiE5}ÌDUgVÉCâûú.ìDù›¿~Ñïñi¶ë‚ú¢ìÀ±¸šwšrŽ CÀ›ŸÀq „\n™z˜kÉ5”_¾Fcj2“*/I¿Tv8c41$Í’³u;ÖTù}Ðíç ìʉ°½åIß«x…``2Ý›õz:õkäÂeõž‡ŠhgH%Gd«&E¾’Í 1•ª8nš¢w݃¶·uÞ(î@¸Ò˪6Ò# ‡i¡>-·Üm6û´Nžx–ƒâë;Yš»“r·^=Ï^[ù>îÀ#–1¯À?Øž>³)•¿¡¿±sA‹¢ ̨4ùK Å†s‰þ¶¶–&y "¥ÙÀÉX-—1Þ©Üë6DaLbÛ¯#ÉxÀ3‹*‚~g"ÿâéI Nü–£†Úœ¡©8Hm£»P¤ÿ[i]~i)б«Ðçvya‹N,3ü$ÑÕZÕÔËš¯0›¦Z媜ÇeÕ–ViÉÔqà¤<ÅÉ^*-Ù˜jߎüeWrЩâº%Z›Ì™0WFÄ âï]«\긩Gi¿Qú ä/œ®7¯3íK?;”têŸKZ8};­‰k:‹EËXº¦hœ&c“`8[c𽿆5š*Â…Œ„¾ùºSÒߣºûîn€x ¦RW.­³´/„ÃEÁLW/ æ4Ƶ~L)®²3šF¶×Þò8[Ë·ÝE½+}¹/}‘”1‡,&zJÛò&E„<Ľ–êD€: o>Uõ‘¢…XHôáw7 ¢Œ!1£ÁÜÏΟ«z1˜T§ÿ æF¿ƒÃ³ˆ°lë¬ 9ª¶ÝYŸq_á05Ê÷«Ä;ço.l5êÿ¢#‰‚„©ër€}•z&’CþÐ!ô;‹œ8‡t8à$؉µ:}‚ºm·çÓìû|ۈɖآیˆ$–Si5Û "“+ ƒ‚í1g$Hâ¹Q Ÿ½è Ì—Ó'¸SbCe|ÀŸaµø¥u7IŸ_Ì$>íyÆ g¬“DŒd*2{”<®“.bu/_3zÖ5¾´~Z0Ê}Îj7æ°gdµ¢roÚC¦SÒnuSÝÙ¥t;ŸÛîl›¼Ìo8éoBæ¢N¡Å½õ§­Ö.øJɆ88-OÛ£Oï\Nd·Ï±Fq87du… bc-싪•5|¸¤5{š‡X¬ºm܇ʈޗo™ð†Q¿wû^ª9¼"*$ŒK3Ç Êµ§ÙðX¿‘/ÔÔ»|{Ó<ºL¥<ùLe5íøKt-Ð1ƒ,6ùt̶”LìëIe³ø¢MCñŸ2\ÄvsÍmçÜä щz0¹œ…Õ<ç¶ÈTþÇ.¯Ãò„ìUÚçíó®«ÇPèÛ‘;Ñnù¸Of%ìÑÛUø=Ä,¶8¶EZ…²¬(%ŸJ- _¨o}¬ŸëWêäˆp(bÕÚF¬¬ØÌ#SpU”4æ }„zטÅrñÌ›)R&LÏÑ2u)M5IO³"9ó‚Nê-<\|ã\\£LŸãNÁ ÀÝ9KSΉ¯Å¦Sƒ‚õNº$åN›À·¶–_'o}Ú—L¨OšÝ¨±sÉ$£C» ¨ÀPî×1 Gã®t›4 N‚ ìæã‘eÅ…Í û‘ÅAÚV0–ªÙåAM“áj¼¤ÊzUaÕ`âÔØºs„ÕØÌê׫¹Xö²Å\ЭòuŒ~5ÿ¨ÿ¨{OxNm˜a£A~Û Z^°ñ¬”²®n ­Xåóìî© ¹h™@"V\ @E'û¤µ^ ¯ÛÆû ‘¾Vs‘ª!0MÎØæMÃ]>ž£µ]^ÓÐìU’³3%ˆc9u–E“­‹ÅñÓ/¿ü“øÞꪼa»ÔRj¢ýXÄÈ@95êà»ä*ñM¢Åbmnj’P£¾áA²§ÓÚÆ ðÆs¾NGÖ÷ËÂC®{ü¨ûÞ+§^ïTXWhIýÞ{YÑ÷\»)ÍMî´‚·¥1oíëžÁÕl¾ø 4&nbR «’˜kcõ5Ë(s}?à€²{ûìŒKvÄá$éòUÑlûz—HÚ$Ç¡2 8Ôì6„${€, xmY¡Ò˜ÇTQx Õű`Õ8e¦¸Áƒã^£þ œEÖ’ Óê:Aü8¾àF+ľlŸ™ÈÏ7Ôm»ÀØÒþ‰eìdß»ÅJïËÏø©8Ë—¸cß¿Vêðÿš§Mvâe`œ€OX“úL”ÕiöuÙňñ>@æWŸ+š—¼Vrz¸,Ef1ñÁŽ©]Bjƒ) ¼¹>:,P8TMu—cË ÉŒé?Ön°N¿€Ý˜C]“^Áœº.ÒÂÒ5×ðg ˆŽ³Ç$Äs‚cfp±ºÊÙ —)' ¨=À„ádCÍÅ^¹ôw\µÅÄÏ=Zj.[€u5ë}3Ëùê"Wf…¡»’™œŒÓË4ûkôŠæšËôX½?}hï$·Ì·©Ô†!‘™†®NÙ×o~èž Äß”/x¦×A®ˆ.ö©ÛmŸZe`1¢ûì8Çço B„>=ïmÅ> õÿÙ3¹-Í÷”ծϴ´”í—èiåƒ~ÁƒªÙÇŠÁ‹Ny:GÏ6ÒÆ©Ojø »û#µÑBʉèÀu$žêÈ÷`ô[Cƾ‰“¨V‡úÂñeA 7ÚÌ+¾dAŸÂíëz9CÍ)aÚHÿps½®}´«œ¦+»85©ñÍC]è<0ni¤€=Aó½+LÏ#;¦ÂŸ¶¸#]ØcÀ½›HxïÖd´\¼îñ·}²öüÿPy«Ã°5³6ÁTzJ‹zÆZø+œˆh‹ýõoè®îoB$ˆ‘µrϸ•a¬0ëÑ¢†ÞŸny‹FØ%¨ÊuÃ>%ëȬVÅ7i5Øo]YÝ#Æ>›­Ñ®ï³©ê½Üšq)X´ÍIG#MYÛm9XÜ`T Ë:-»?†í­ ûZJ‘¬I{aÝ…0´ŸÄ à\“—k3sÓo¸HÜ®,¬5ÓtÓ­%sq£mÕ…Óö7¤‡ "{ ù©)禰ŠKϧ_I´Žu7¶’—°OîA?Jš´´•nÇÎïà‹“²Ï]Ö5g/ð[í“Y®«¥Ê)W;iŸ;öm.ûM݇zýJV öç•þÁ ·>ãnµr߀ðU«±&#³?Ñ.Ân¼½„~4 æ‹¨Òæ¿|‰ÖƒR½¨ØóéV#Å<ôã#——»4¥ÒÛf*Ë颾ȵB?p´Jp>ü r!ÕP·¢$7âŸX%òù'^[à;¢Á çZ½UMöiw ßÚÜ=̵ø[.² òDÑô’/ÃŒ úƒÂ8I²{º³½(}0Z2ÔZ`(§ZâçÂ$]wy¹­Ó,¹ëE4kwq¦è:š«4MqWÁògÿ×öq ¼£Íõ¦¼ãùÜPÑ­ÓÏxñ=t÷›$ÙÈñ2( YïS m¹ãèˆÚúß’Æ•+⎹‰o$ÍYßAÃ"¤wî–žâ>irÅÒ\õ PÈ´1Y„«²ÌòdYÄ­€û{ Î|Sy_ÿÐFÈ I1¹êèqÐOÓ¥¢>Hê4èðl‚%+ÉD ÒŽaî#º ª\mÉ$y®÷).ÝíoÏ$òÈA]ïüÁõvò~‘¸Ìa–Î|æòí¶»9YiœF‡A*(kÎΙXu‡ãìóu.ö|ÔEûëÔ—OØÿN§à$Ÿ÷`HBW™ol•«xwŰÝ=ìÞ³ÈG^Pˆ¹hùze±l҉ƕ=LGA‘Ro“’¾°ïŽ]Q„„ö¸—^iGž_Ô/“b'ÜMU¹FMp16ÍncüµZ6Ñ"ðú¿²NÖ¼°qu!-zÛ ÕžÛþÆ]³ÏÔ (GªêÈRs·¬‘'äN‰Ñ,ä~1ýE=ξ+ÊÝ^õëËì?%'ÿÓ€ž0šàôÊ )·ÈnÖ´õÜÈ©5rñ”¡ ‚nŽ¿øÃo¥ì¢h;ŽÞ  »õCs‘õ¹·|Œ‹ õvŸ²7€„Àõ3ù`8úñé»è•ÂÑÈG:áù‰ëó]¿”Ö„z-'nöicEú÷!g»2åVlÛPT‹3)ÀÝHÇÈ׬epbc¤xŠÓ½L¶ÐËWî“»„—¦!æ¹<ÍĽJÜèož|éíµŽ…ÃÉŸSäÀ #xè`­+¤âÜ0塪þЫ W,cç‡%k&ßݨ7ˆU2Vý{òïÝÖuuqF„õ~ÒZ¿`#´ÓÀ—‡jïLÎÍ—9¦¬þ—¤·w¤àn€­D±*×òéã<Ó"QÏ݆Ҷ\Aù2«þ URûA[6ëò“ÔùjûЬߨÁ,Íñùâ t? Ë©RÑa£±rÜ=³™ÞÅ€cZsÊ`Ð )8æà (Ü¿Üe/ì0f£Uzˆ¡q̤ڊï[>]zVCDV½ægZC‹B{á¬ÜPÞFdÔ"üÙ[íî°æ—îù#qC|4ʘºëÀKI&àŸ½®Ws\Ø,‘ïÒµa9Ë"¾<¨h|‚ ·åýqÓ‰À1Æ}0»ÉÐm­ñíÖp¸H¶ä{›\ª˜½×5ô&Hs/ ¬ÝŒ+)qzsõ× Ÿ'¸È’›ÛòÉyŸ¶ÎxCõ®4ç˜fæ.)5®Å*¾Ó ­~µDËTAI ‡æ–æ¦Î—a£°Œ¯¡âPß]RBÉ™Slñ"_ ¥e!žëÙ;wõR(ô èÅß:ÝtEuŽ‹ÑìjÎÜ%2!¶°–NRªh¯¯E.˜{$hv¤Æ ¿“Ör…{Ç>ê@Uõþ½1œ'O¦ªtŒn÷9B‹Ø56¹Ý3•áY†Úˆ®âçª/tºÁ/š`ü³}å´LPËç’ãï,ë:=tt(®nÌárâÜa÷œ$,ôÇm‚W5Z—fyÍ=>1Íëïñfº¶AZ¡{ôöü/¯.^ýùÔµOæâC™Ò³‡‡>CHôã¬þ‘÷øà,ûvõ¡R=J¿þU×"QÙ ^@chPÚ³Ú&"¸â8{e$U™L³ÝIï«×—ÚãÒjú¢oÛF(IahoWM6­)ÇhÇÔ¯WËp}Ñ›Zû¥ãè5,Å&×´Îd_ÓvÎ ãcì?júÐñˆ3N´™HGŒ£’—?쫱àãìm Ï=Æå–~o¹pUkl¡µ:kSǼͻ;vMmA¾kÉÄÉš‡îðXó%-ñX(!àâðH±¡•rOV“î@É Õèt"÷» aml` z‘ ð‰ÿ*÷“»Â8¸ÊP’,'JÔ÷…¹â’·œŸb>CE4ÁÓxž}ch$§æÞH”øyº”C[Á­òú¶â—º—ì’uiK»øÞG×ÀN$|ÃElyÛJéšÜ*@Sqâ¯<Ñú¤•˜…$|ÄÏÍ¢Ã}‚°Á>¥Û†ð ê½+Î%]馔ÓyMššXb¬°שeC؃¸Õ/¾cÛwßѵ¶BÅ÷ܵwº«)w>\ð,dSÁmÛ œ¹ÔÙK§jhFŸùUº—NFDaœxüQþGk|å7að1^%|*°e>54ñÙ!ú†KóÖ`£= R쫺›PÚrœÄðè¯ÕÎõ¦]f6ñBêCq3ª“7.wKïjâ³ã6|¸Â|·6ŸõKòEµF<¬ÏŸ[%×:Ëqоü†’P"69¥x 8%+Œ‰¹æÜ…¦ç:6% $ÝzÎÏ›Ðéˆ%?¦¥ž€†-ûý7t5©V'„ÁWt£»%MÕÈlħóaâëò%gŽÃNÿ »-¨ xr¸Ì݆اf›ß—šÙ¼ç>}оÊÖÆ]|'-I;¾mEzȰ5°¦­s8-éßu±)°Ï|"­ån0¥ö8®^Ãý^Ð_8(™Ë‘Ò4GЬ蛑3È4ÚÍ)Çv»ÜÔlZMÖh™øÙ¤’²´!ä«J>ë>Óóî¸ÛrE%º°g@ZŸ]Ǿ,{ P`•öÀ±vê ‰@Eu®}ûÜßAYW¸_Ðt«:ïá˜ø§‘ö:;:ï-©ÅÃÁ·ô3ª+>¡ÿuÓäW‹²]O‹jÑ®‡Aß#/yþÄ,¯.^}ùöâÕåí }¥?4;½Çøœ¨`BëG,ˆT ú·OÚØ"T,4Cþ Ø^·lâØ'.ƒ“j lÙÆÞ›ó«o±‹$*¬ Ð}sÓØÃ»Í= ¢¨ç&rùÄ?v©{pT+Yá6ìdÈË*WùQÿLÇîß%×;vU¸P@ÓᾫgØ­·»:¿ÎÖ™“wýÌn“úZ°ó}mج´bsñæeï—hÆ~ðË¢ìùî_É©ø¸e•,ùì¶m·ÍéÉÉ ñßÝ|J;x¢¬ò„ è¨'óu5?ùí“ßýq•çO~g¾øÓoŸüþ÷_äóß/ó§_~ùþñ÷‹ß¯þ´Xþi¾èn÷ÇßýþO&ôŸ?$“°J³C1ëŽý/j\eôaó¹0ß¾zýíù«?'Àôjy—óÁ^äì5q§V³ ¬ä¦'ш\ %oÕ…ž Ã*EkwŸBË€âá¶j¥»½Š&¢W°Õ”Þ­~¬q)¾öI¢Æ®lHꨗ Å[Ù%<¹u<ˆ×wÖKä];Pèò¢¥o‡^ uÓb†`“:*Éž<@ŒáU¾ît‰ã‹Zo#:Ÿâhÿ8ʆY]Á% Y~—\:þŒñãíÅ7/ÿóâêå‹úíòâϯο{ù"t^ ¸ej´àë·ßâµË«ó«.³“ìíÕË¿|O¯kÂTÿ]xÒŸ‚{LÑc¬&ÁíwÄ-ðÿ扃˜ö›=¶ÛÈæ2Ûu¾á·tùB_(Ì=ÏÞØÐ+mïó^|\V…½!ƒï-'Y2/ÚšÓ¾91,cîRIŠ´\Ðwœ6ó±Kq‘ƥȗÅduöà©ñ6ññôßßô³5 еÇFP(xÃïfôNì£Y;ðöô?}=¢ÃÐe_¾GéÃtD]‘&øÙºE.–)ѱ°¸ëó€ \üh jœ©{<:ÍÉÓì1ÿÞGˆÏcŸAp;.¶çêõ›7/_<§åìç’­¸Ì÷Ï!¹û¥?“b v]î8z3ÁD”»“Ü ÿ¼ŠÔàþuAío­•lqÔåÜvÕÖJ(\Ï„.ÿ\/ ¶oØr'?˜;'¢å0¦˜)\/­2ã¢>׃½õ¥çZ±ß©Tõ¿ WЭ^Ç$Þ¯^¤;x—<^ÃÍ^¹“„]ÅHv’.è. ê\⃛ /-3dï©/ª(m_œ\î‹Óà´Ôär‡+b6•8Ø”»µŽ:ó׎wÀ‚YؘVÏ@K6 W¤ÓŒ$áÉ mñQ¦…<= ÂÚ—8Ùð…OOð=—øò¿³Ÿ~Fº#„k©D+Z ºÃI† £ ¸jö’]Bê˜ø~üÏKË`^K´Í†ÄCôÁ{'íÇKワóg^!Ó,ŸÙOTÂ~ˆR[ ÷8h´diw#A4¹z%î5yž¹ Y´BMn¨"('¹C°`¼Ë-öò±»·ý`< !ÇZ"ôk󸏲çÞötÑ ³°ðÀ"žw¤ùdkw'ÆRÓ¨–|ûÅ2¬ÅÊÂÖÅ¡¡o[ðó cYŽð2Ò|qÃÉ@ç3NwgvÅèùÂÔ'ݧýÝÁâ¥2H¸Û” gg™ß3ñL'jK‡¸‘½Œƒ]‘p£Õµa†çðLËš^š²< ï/p~Ml¬tè‰Ln+ŠŒ»+´ÙÀWª¾Tn¾m*.¦‚>­Þûù9®>h´yØCÍŽ‘qF¯/ÞOhy“eQstn«MQÔ­F4Vʸإv/ꥡ9§ºÎûˆý>Â…ôèzTä…Ùž³éeý·=j=ºöK¶­€ñãƒj# ÍÙì¿ ÛFšÅvü…{öÿÀ$SðŸ¦Dù-?0…T=±|R4‰§OOŸ°áFŸOoÛÍúø‹›]Ç¡n§2—·¸ýHHd¿^ ]Úžg·æ=@N¥ºŽGûn÷Ûbäƒ6îæÌ^cÿ—ØãGGÑG°ÂÃì]lÑcì ?Ѿ|"W¨=ñŸøØ¸ûˆégÛz‡A}Q0£+;_†7Æ_Zîuf—¼Ëxu&k‰¿BÉ&ðÄ]|'}]‘Kâðl¦³ùH£‚`ÞA¬êšDË?M]ižEO|êÊõ vYgì„Æ]áGÞrlkaÀry}˜²ò¦NÅ$O±zNbùðÜι§³ŸŸk ;ß‚UN³ï‘m#ÀÅ…nŒpFá&…q>$]tçf vE‘ƒ œP4p××Nà®ZQ]?=ùyzd/æ €>äLÁÞç2Tdï}ŠÔrǦ¹­pg•xá ®ú°øº_®uì“wñ,îø¾ßÓ‚QiàêCÇ¡b»Cï]ÏHÖÑzIä=úééÏè#»ò|OÜ´F øv8a‹1“æîˆ´#¼9òÏ:éî•Júä  ±zÿ Ù9%1–Ÿ³™egÙ“O§B~üÐLV|ŸîC³á®®<½ÁÄw¨Lᲂq§ºÊ¶ø ±/D~‡Ör|1Á/-’uéB æGXPríž® Â¥ÜÅI¦ÉTbú0ÍãýÆZÚ˜],ræŠ^u°ýêíyz‹®ÏûéÉgõÏþR }õ§§ôišüøЄ:Wæ±kÑ 5Š&j œ¥ŠÕ^Ø;c_ˆ–Õ}£‡úb„X¼w¼Úóõ¿Á›mߎ}; ßëù³ûÿPK¼t‹Iù «¬¾C>invoke/tasks.pyUT ´VMXØWMXux ö­;koÛF¶ßý+& °¢\‰Nï~s«n»I³ ¶m‚&EqaÒHYŒ)Rå–Õ ÷·ßóš¥d»‹õ‡D"gΜ9ï—F£ÑÅ»miÕ®)úʨuSwº¬­ê¶ø¥5j™¿Óö~©Ö•¶VýW<˜º4õÚ¨ÂÀÝ5­U½5…êš‹;SxdTmªƒ6¿Á!›¶ÙÁæýQ•»}Óv°Ùìñû…|‡S÷fݹ¯Ýqo¬lËáÄ¢iÝN[>ºˆ¯yìÜ›çüU^îukßõ]{×ïLÝMU×êÚV€ä¢¯ ÓZ¼'Unrþæÿz}¡à€”i»¦©¬óG¹_TM}glwa*k>·¶Œ+m“½O¯Õ[À§¬M¥šÕ{¸»jÍP¡‡wJ¢}uT«J×÷êAW½QÙƒUËåÏMm–ËI~ñóëÅ‹ï_~÷ëïÔ\`dÍÌBÆeütÂh"'ðÿçÈÙ³gÖÊ<šußéHòX^vVi¡žB&•›r­»²©óêSõîõ‹××Êv$Èu€Àëî+³­®¦pêXÓ5°¬et;«VM·U[Ó§ëäB}‹ˆäɺªšƒ§ö¦Yc- ¤®èîˆdºm€Æp¢mvpóÀwSeò»\eºïšÅdߨo†8Ò»Nn*}g?¿~÷ýµ:e·º$‹U_V@>»Xä[Síé¹zÕ­Ú÷-€4›¾ÊÕuF€°§¬Ën±È¬©6SzŒ«¦8†oµÞ™9ò:<ÒU©á¢ólž”‹›½<ŽW +æ/5o8½ëü]ÛGoñVà 4Ã'í†èÔ}[ÖÉq"‹LÌ_ °o ÜDóÏ‘*9Äÿ‹6üÔƒ.‘)šõ’ˆÈ­Vf­Á©·ûmY?*0i¶ë÷e¡ôªé;@·±èJ#lô§,g±€ƒîL§»®Íˆ j,/Æðq<îÁsÏoâ7ƒ]OÕ &;ñ$«SQ“™„ïvJ‚~ØG°W dÛ¦¯õ5™ xAª DP)°r¨—릪@á\‰Ds:(}#²ïäSúº´ wÔÜ¡æô EåJ¬,P½@‚ èɦ¬ªǪ\ƒô‡÷6 Ÿ´vBŒN>®‘J.^'}’.'“D+ à‡Ñžƒªõ Z•E ep¡)‰ÿÂΛۓÛ"¥è¿3¯»½@IG9WÏ¢cƃ5$eãØõm-&ÎØ(Ÿ0×ë—Üš>_КoEâÞæ ½‰PNŠ$POdü"²Y¶kÅdE ‚‰À?ô¥‘Œ…Õƒ*ûðìãd”ƒ0ït—¡ÚäÎâÝ“ÉßÑ×èÛÔ‡gOÚ¾üø@ÛœŽñæøèëNoàˆjLoâæwg¤UƒLж¹;ÙžÌù=}K¯+àÉF\Ùפ® ™ª ˆ6 È™jFkKë¦p¾ÜðžÕ½Bó—GвWpIcÍC ‹Ð—³«#Érvж䨠†`03»Jôõ@ÀÔÈ€ƒ æ÷ÙxVØ[¶ B±|rB6ÖŽøí,Эøç!|r];ØmÍN^àFl`ƒ¡&‘+ó(M§c_À~r²+}b×fA%ØþXe¾oÛ¦ý$îÌûH¬¶ÚnOñ©z,8Ë’²’W ÂÃjƒ ° ,ÄE®~hw± ¼i: ã€×G×®Ù¦¼G‰†îõÆ¢SA Ô½9‚óAQ« F+Eë0/@/S7‡|¨,¸=hÛD}=aúE·Eñójt !zyI¡¡M®ÿ^·àöî0éHjQ)@lX7JBþ<A¼]iq“†¼$C¸7Ïn§.˜¤Œ1m‹¶‡LˆyĤL²v‹‘8èW7e‹Q{{7Uwì¦(ÑÅ“QÎ…¤­.ÀZ­Þ Ø~Åaø¢Ù,Ô;HgHFT;h«_“†Î¢aä.4I,¡e×ìIž )ûôÅ\}9d)Cü”÷àŸóÉ߀‡ó{ ³‰G1ŠÜÆ/"žC3ƒè­óàß]ª—ÄȲvÞU%:Zî¢(ðÛM ˜Ò"¿H¨|©^å—Cä~¹Ö5ÈÌ%˜FޤZå•`¹DµÈ&Ë¥Úcn ŽçcLÞBâ‹©‰ ‚ÕNÌ®ÇuÏEP’ßD6VAfÒ·%䵛ݾ;ŽÅñpÁ¬'—X¾˜wãç` q»¼l5Jps¨'ùY:?U?€mÁÜHà lÀ (2söÐyœh K¹ÓfМm¹Âc°T0FÖŽñî_¡;ïtÝ“%ªËGõ¥íœxxÀ-Œ)F%Aj‘}Dâ}&à£Ö°MdG”„þ̓Bœ%Q@cŽÊ©”;‹Z6!ßC;rw¡ -ÀI=½„“_<(h<]0£ç´''-½q¨ïzkŠ>‡57-p Jô8!»úˆ$¸ñ[§ ÅçÊDÞÞ&x-HÔæ$qYTNÈ.ãæ m’…y±É$ñ{gI}nï_Ÿ¸aY‰À1Òt‡˜wŸ= `Å)¾ª óxÆ!úú€1ôIQùÅKÔ”=Žä>àç,ú@dÞgmåˆ]Ú½ÜV? ×T˜\ƒ &]nød-LHzÝðvhÝü}§aC0nŸÎiØÜE™Íu,;14gÆcC9Cž*ˆð"è¨/lPªŽ@:°=•^ßséè4acÓàB?Ì@dÊ5kFÀ 0ÜáˆÄÃF¹œœò6Éõ¢Üÿ(†@^ŠÆ_£§oð›Øi¶ÚSŽ‚‘#zºY¡·$¤sùöd% ì#¾^ NC´û¬8D¸E5CAËß¶€ÄÔq›s9$Zß›š…'â>®‡;$¹ço>ûǪQ &Å_]²õfÞo%·Gr²î³Ð½·èÍÌçÖ˜'ŽpïN8Ißúڪ䘕A¦Âòv¥  Çhûбg¢²«’âã4‚†aï®y b´6€ß°â·X{YvV¥©Éx1F´Os2¾£ßí/™,’ÊÉÙrñ@@œH£OƒÓŠÄÐ,RMëÂluØjªþ´€Ôèt™ÖÇðµ 2öüåtÔ¨ŒVÍ…‹Ñ®óÂ9¤€u*ÛNÓ"ü[ƒ¾Èmõtã =’ÐHýEá*ÄÒPFÑ­¥8RA6|‘k;ïh¸HlMGµí1ž0ƈŸLuuÌψ­A¬)î–s'gÊ+Z{jl0Õ>IPbÂÚÓ9qÄçϯˆ“˜!\ f±Î¿Ùa|~\ƒò»ÈÙk­«.$­rƘz=²å]­a·ùT8I ñ^§Ò¿ëé^¯° GøL.? "ê ˆ«kÈóË:PH½ZjQ\q)ƒcZJ²³€•ÆŽ<Œâ»(B¢ìƒ4:{ŒÐ¿y¼b7ÁÛèì7m¹cãäñ—«+Бâ8#ÝÎvÅJ£zdr‡õ¶KŽÎŒ‡ Æ#Â1è/‘µË!I èñbÂþ½/±¬ Qá–9çÅaƒ Ë›¯5ýžÏfFF°L—ÆÂ6uüÎéOƒv¥²_›ƒÄ•N³ËK'#ìZ?ãTSÕD4¼g¸“eøu_`Ý=¦Ñ‚Ë æàcEûMeT]\2”¶åz A>$õºê îyFŒR¶Á*€5¿÷¨eß Ãlá3I':K`l¿Ô@E(ç=Ý"CžË=ó!=žB”vÀL¬6ÔšU¦¶ ÁI tB—TÛÃ<³&uÂ;M%’Õç®|0õ™hµÛyàÀ[XáboŒ-©¤EͶa}‰a–´˜´Ç"ðQ8J,OHö48w`û©-2U²ÉÔp4ö£³A­Éý•d^¸†;ŸËqç! „¦í²gSþŠ P9ù3^1ä`»/Ðp£m=©Ù¤}ÚŸt‹•/¬è]þë:·:äý¯ê‡æž¶R¨ø äre¤lCÒm00•Ò0Âî¬å\"Ä8Ò^±^^Vì‚¥á‹õÆ×C ýš²¼4è_ÑHÜ›ã¡i ¯;·àkSˆx°pö¸@Ví¤Às©–KäÁry´Ê,ÎD­À7“gjàÞËü¹ïx-sõ+õX£*¶~hJZýr£FLîCÒÒZöT BÀx™ÂØcÌç?¾RˆZǃk]©«ÃÉ)~ÚÒÌ÷çÜ)a§¢x1éDàÝÞ1±šN‘؎ܦ´CPr½Ó2õð³n€Ä«éà©Üc,Un6¦u¸ÍÕK€e5d¥Ø áF>¾*Ý¿.— ŸæJ˜×ßò[‘º– \SW$MÑÙË刖K§²Ï±0ͱC4"áº@Î\ ûf¼;R½bŒ[,·Ñ•mtOž2§.›ö’Ÿx |ŒÒüU€Ô HËBrãl­Ï…Õˆü˜Tš4NE¹>›-àŽ[ýP6mŽ™ºŽ£$±(³2ÅÊs‡T¥´4JB8»|=œ)À¢”Ue<Âc4 îèŽÆ8°”è\[Eš±6Šx%ÅŠÍ:PÚÅÇŠ‡‚»¼/¥é&Œ†Èý8øb*ãÍè²#'•h®[³¹ö],÷µ{0ã߀^¿íÁÇŒwdÕ†(s6 þuf,£±Žår6Ûga¾;ÒºårŠZßšÈ,)Ö "Ã$Ù¢Õ WÔT8åïMƒM¦‹Êd„ÈÕM8ßK/ÁnÍé8Š,!4FW¬ì/àæ¼-ˆJb¥¯È†~?ömDFi561üåFF¹¢ÕÍ¡–„ÄÆ”Œ-Vš¯"¾®‚Àõ²Ái ÃÃá@eBQõšÎ|Æ Ò(Ã`òƒŠz$`<4„©Å̶CQ“÷kŠÓÚÒî+}d•BV1dNpßw‘©hI Èf_~±Ê’aøD-xƒ-z²iT´ÖPÔ ¡¯¦Ýè\¨iÇePä ©gKDaæŽÛ¯‡0’›»þ("‘m::€yCÙ66Õøâìïœ/Àã­1y(5aÎÏÙò¨})qÅ+Ê·@êÙy¯O²$füθIéOl¼æ dNŒjaÀ3OØ î×­jìݱGÁch^LÀP …>¤.{”Û=xálÐ#(7‰M ÀeÓ=&.Èó1 ¢V [œa̼NOÒUÞ¤ë$™ž»èÜr÷|"Ý D#cûüùžÐ c×m¹2VpiÞQªD¤ N3ÛïAºMqô†¾IÜñåúlª‡¡϶ìëT¦fx ýV…tk·ô8½îx”ÆÐu(ït®ÃÀidÛôà:ŠÞ8Σkµ®ÍêZ.¾l%3?U¶û¤ív«Ì¶ûj}÷)ìÉjôÊýl¶áSrz,©ÊßsÓ=ùÅɺÆA³ÙO·oV¯o~}'®ÄÙ7™‘{Zøü›lo«F§›Æ”âóKä=¼\ø³Ù¬¢ #ךV­‹Ùâr&ð×~Í×Ò«òk6j›Åñ%=¾ê–üxóêÓ›eÿrcÝ^†«Á±ôj1›‹k­íApxaG>HDUѺ‰–DöàE€UM•¨Ç´••°¤¿÷¢ J<( k~GZÃÿñ ]>S›ã«rÖä[ Ùü݇Üþíf=œ·‘Æýª*\§ÀbìäÖàVlµ]K-^_Ë’rñžvù K{‡Îü)îÍæÊ<Ø{š/f€C< eD6Η­É¢ÏŸ¿ÀöÊ\[Kñ¯br<Ò¾Š‡ÆÌúvïÙÙYüÿ1rЈ¢ˆï‹B¬Â ^¶ìÄ·`!½p´m´tÂÆÖç[ŽBãL<‘ªtÚRÜÓñJËýº’âñRdyi`óù‚½d7ÏÅÝí·—â½¼'QsB‘®ºYkUŠë¿¿ƒÁ=µ§Ù÷'UÂñ•UvØ‘£6®òP dhà3’ÃOñ½ÜUʵKã³àŽ—=厊t¿m”‘Z^õ{a®u'ýj£4› ’ûTÐ$>¾@n¯  PN3Œ)O›FãyÞ\àã,ZÉsal ËÁ—»ò=«È[ûÑ ¯[õ€T±­ ­€§]ÿ‹Jè‹4bM|ZÕ[;(äÄW¶C£©/¬S”«¶TࣩFLöM¹c&ž4 CÙÑ„õÅ2n)B8¹x öî­9 ;ë oE U `ÇRç{CŽ~m”£ê/L»ã+ú‰ƒCœŒ¹òàü‘AckEñA=‚—`“Ü‹´öR\?Á¥[—¨ë¤‹âÎ5ÄVG‡ j¹Îà 2Ëáta9×å©·—N2¥n*`zàzqAËòžÙPé <çw¶ZLKlBÚ¶Þ”W:hJÊNü\²o‰í©ˆì ¡ÖM ç,|V6ÿd|S×±hoÑ$½xrJ ­¥ úÿN}N¼¸»ûg ÿÇšJµQ%Þ9r  "ü€Uè™l'Ž©M"ÏF*ø“Q¾Í!ýĒ˨ò~ß í –>.°Gë5ðÂEžl½öÞ÷$†m¨ÑRHTÖ̃ehØw8ìHȵmØø€/ ¥A@Þ[Ú{ÐãÂS|Âíc³ØÉê$«¾AtŠ–ü÷ÖPœI…>x,ƒ÷èRÇhUÏžu8æè&÷Q;ò@CÈ¡)[.jOMe;9Xp”†ˆ»4µ8R™Öë$dR«A[Z϶x¡<@zÐ’”L¬×œÂÖ¤0–·ÿ»„¯ÅÚZf#w12<# H‚г9Pl,%àÖ­Ä`L1ÙÏÅ»HdQ¾àÐÚ…à'zª i½Š-#vçÈRÌÓºù"™Ýà¡"›ýižÔÁ©ÉP/™G|¦ mnR6úšñ6¹×‡•$B«¯w¶‘ý®S¹¹Òl-N‡SK’¡®rP`ö$¤ ~¢„9ÒÛ8UE‚xßì©5Æ›*é8oœ§È%D„:)È3Uj˜fÝ·›“|G嬟˜óôàDÀÒCM(»½Œ(á£â¡ œäÒ‰…ÊËÚ)\ wËÊW(ÆŠK35ìâÚa¬y‰ ÓAoÓ9¹øˆœÁÛ-yóÁzˆÈùŸ¾ûó”v¬Á«•2*¬Vúðf)^¼¸?H·õ£tu«£*ã¨ÀŠbè0xy±k‘éüïIë}„CYjÓÝìWœXð‡”V s& –yÀîë§èÜCm1~-”•òµ–ÇÄä@ÅŽ©A:ɽmÛÛ)e<ÓççÂECÃýpyèÍ"ïì±ë÷Ÿ‹ñ–œ;ìŽQÁ»ÔðRÒ>Ê Ûô<™`,Fƒg2ÐȒǘ…‘)ÞÈÒÌ”§‘þš˜ÿ÷D5zIÊ„ CPìiÞžt%x$Y¾sܾF—<Æ+ÑÍ5&[L­¤a%}˜¾„ ±±xýÕ:ëiƆ"\¯&3ÈöÔ£váòè÷Mž‹ÁBÒ˜.m»pÔ­cêÃI¸÷_Gç&Ë×A¾w{ˆ}“[fêsœDÜÒð€3ÚyÂHT6aÌÆd«vvË k×ÀµƒTñæÄ~7±—W6Ý`X¿¦žì=ßýÎnL¼Ñ„Ùâ·ïþàþÍÞµ¥Á{‰ggñJ˜ÁRž®ÍÙ‰Ï/¿,Çéÿ<Ç…ο€þ|C[­ Daì¯rHzïÄiêÇ5•G»üµeÙ8Ǫު1Þùéè­ÁV±5ö¶ž6oþ»~Þ@¼ýucÝÐÉ‹qÊ1òœb&ö_²/–öW‚^Àc–1}p|`ú=K§±ò̓ë†G[H(,=¯KÜ{'Dkxß4î¿Xˆ'ÏNÑÈF¹E˜_$|¿C±²mòi|_UÜ'¹?÷a × L”èŠSSµƒ…ç±v”ÓO>{|(C¯“q^ãmÚ„Eß ¿ƒ¸Vö`ž‡ r|{wsÉM8MÈ;µÝa¶m ÏqÈ•<´ID5é ®)4­õX0x [D5Ý6¹2ªÀ1©sñªá)昋_vGF`dmÍJoÁæíyâñ;¥z83MzOòÏ+§š»Z9ª]ÛÚG ê~'YmÒ¬ÜÔ¸[»',ùý‚ŸýPK H…IIinvoke/vendor/UT HWúW^XMXux öPK H…IIinvoke/vendor/__init__.pyUT HWúWØWMXux öPK H…IIinvoke/vendor/fluidity/UT HWúW^XMXux öPKH…IIžJ˜qiÄ"invoke/vendor/fluidity/__init__.pyUT HWúWØWMXux öK+ÊÏUÐËMLÎÈÌKUÈÌ-È/*QÐ.I,Iõ…ê(ƒx: %E‰yÅ™%™ùy:\ øg^YbNfŠs~^ZfziQ"XL4„xsÜK‹RüòK‚&§e¦¦è(¸åe§"™¡ÉÅPKH…IIÃòRh‡)invoke/vendor/fluidity/backwardscompat.pyUT HWúWØWMXux öËÌ-È/*Q(®,æâÊLÑze©EÅ™ùyñ™yiù v¶ Æ:šV\ @’š¦œ˜““˜”“ª‘Ÿ”¢Ô’Ò¢<…ŒÄâÄ’’"¤Ž‚z|¢õ»ú6q÷0‹t¶˜jo%¸¸Øûs„Ɇ8¹–8pc;œ;òðVÜÑÝh|^B`Hz˜€|&pôÛÓ¿cλÁ…>Îõî ÎÛL(Ç?A°ÃÀÁ¡ï9뇻¹‡¬Ÿh¡ñ¶¢@È¥÷ÇÏI\`ûó4¢¤g:+›Ù6Bí{? þBÑZ?vŽ…ïŒ,5;ÿÛÎY®7}D«W t€ÓÇUo¥Ð7Ã;{[꺑ôg"ùñð®àä§Yïÿ˜¨¿P«¥Ùp-@ÖPiõ"3‘Á¯ñ}—ÀFš•ZÀÍK³µ^ná‡,³ÄÏJ‹º¥™,ª\ Äd™æëL–ϰÀ¹Rá‡,ñsFR£€oTRÔDV®ðÉ2—f›°¥4%q.•×F¦ëœk¨ÖºRµ@ù iKY.5ªˆB”æUñ‚¨W<ÏIŠñ5º×äRUmµ|^X©<.:ã‹\\¥0TšsY$ñ‚?‹yJ!‹fÔvu›• ˆô8þR#UI1RUÏSjó>º‘µH€kYÓB–Z £uℚIp®WZ5|º¶Ð{]‹wBÈÏ‘ ÏS~:^óPKH…IIª°¯ î!!invoke/vendor/fluidity/machine.pyUT HWúWØWMXux öµYKÛ6¾ûW[–GI‹ž‚nÑÚ¢‡ôÐìma´LÛjô0(:›mÑÿÞ’‡ÉN‹ú°ÖŠóâ<¾Òu{î¥bR¬jóTwÃYTju}ËŠ¯>ßF[Pn˜ê7ŒW¸pÿ[߉ ;^`_ú9»bðII.øù,º}ö8'ÌÊÙæ«U9ÀÆEÊ(½u¼µ FHk‚øT+Ï_¤œp&T·2Nþzlïxuª;‘©ç3ÊÓQyYvâ©,³ª6ÌHÚñAÀ?ûZoËg«?Ǧßñ&éŒMhàÄduÖ‡ËYÈ,4jÃ@{^ÜdJ(´(õF‰Aƒqí 6Iþú{"9ô’A~váܶ=A|¿§Â²—Cµ¨„‡®Ê#éù²vBç“z¢H%׸(…ºÈnÔ©A}ÿxÈ£L ¥m+4 oŒ_s?p1fvô2²¥ºH Ujce66rÚ—»ç?eà  yéJÆÁ!‹HkSE( C¤¡à%—Güz‰ˆ @×TÉËUœSž"Yzza¶vmæ±òzš$³Ëh/.gLãàÒ–]éƒCè7b^!>)ÄÜ4;!¤n³40A£ê›FT¦3vìѳ’ø" T›Ø ­ŸšFtáÒ9±>!~&¢u ˆé‹åìj¨]F-ÏÙ7ì+_±ä5ί–ücßêãEê‰ [? x±ö2(¶ æ‰FpxVO½é‘ÃÚ«Ø®W Ò˜+eò}ÃÖ^Éb‘îù™@…¿£¡E Ö6®ßé€Ã0rê÷Î[ac Œtgbø©§{ùâP¾‡Ë삃ڌ³ùë„Ni‹Wó \òS×j¾Â †Á¨Ô ž/Þîöœ¤ÍÛ¨F{S«ç—dš;(‹3G‡jÙÿ²™zFê^è9$8üF¦åè…7 (¾»y‡3í‹Ac)|ß±,‹ÂIÄ?¾Ù€L˧Öól#óhQÑZ]r? 2ý“@Nm˜Å • i”À$Ä„°œ”/Aµ K‚k>0¿ þßzõáûìN¿Â Ã3ŒïÍFqÌsrïœnÑ,iÿ–}™Rþs/?/)Þõp$Q0Ê1è Ô¯O|Ýá,ÑÉp>a¤ÎtC°Mù¹[›Ï‘tJZ,0lþ’KúÛ_R=¼W½ÿÒèóà/jå8Lè`e¶ÁO›ðšr\¸~„¹Žà#M™µã¥Æ|¿Å:‰A¥ÐŽ{s },Ù¨ÅRc ¡ìûªDW­;_ k€'k!X³&¨mvLÎi1@Ú¸ÇÍÁÄ’f.ÄÞQÊ/ñÍè­™¦4À0†„HsÓ ášÂ2uç_§%âó]¢y˜³ä.S~¯“ö÷Kבåià)Æ"Šçž±HMb_­O#(,ðvüˆÌÆ' ƒƒ|€\&^ò"Ÿ-Ö‚Ÿ Ö4éáÒ 8x¼úø1›´œPe²¼Û+%1>S6 {††N #_KšÛóÅtI,é¸G_4C൯Ƭ›CÅ!ÆFât’ä)Ëô’K'st5–ÛÍì©éäGD~ü–@®À—SLÇíôÑ €D Ï]ìöyˆ§ç½^ÆùN”QŠ"¸9º'„lD‹‡Täf>Ui×ðAsçD¬-Ð\'ŽéàQ_ÍŠX–é.’Ûñ×&b¯PxÎÛ%À¶W¼”<(7˜Âz¨ÿ—:Â`C$~ò)Ñå†&#¤‹GUÂB7eÛSµGXîŒQòø@Oµ:•(%¥Ó`,Ì1*-ȦWíN¶µpÑ Iq·º˜}­;ç7õ©gÅžÏâ'){™äv‰Ÿü%à'-ùÞ\¿údf^$Ï‹äÑ `†Z{vLq}‡j×-áýö½þE Ia\¿œ±Õ=S¯1¨‚Gý§PK H…IIinvoke/vendor/lexicon/UT HWúW^XMXux öPKH…IISýÁJö1!invoke/vendor/lexicon/__init__.pyUT HWúWØWMXux öu“ÁnÛ0 †ï~ ;Ä) ï^ ‡Ý¡À°^vA‘è„,7ö4–;L‡X¡ÉŸäGº‹¡‡V3Gڌʒa ~‘áË»õIŒUwõt¤ÓÚ+[fª2N§ßðLðõ*¾Y·ȱØR䉕ªº®I§)ßnŽù|‚ïˆ8€>²`B¿—PXd7 ²i÷~`Š$¿ÁB9Õµ”\ñqÝ©è(•³ˆR-<…œ&ˆ˜FÇ9K¡¦½üïrbÂгOŒÚ6ð:&†t¢GM‡#çú3»z ]ˆ°|,¤ðmpdˆÝjj±…I*?cŒdqi³}g&yçD: eôVê,äˆÛmÉïåÇ×Gá6:ÁHæäpÝö?º\À%Q”: ±C S¬ØÞ¶ÔøÏ3¯þPKH…IIòÎÊp C #invoke/vendor/lexicon/alias_dict.pyUT HWúWØWMXux ö­VÛnÛF}×WL¥Ià‡u<$)Ú"/†!¯Å¡´ÐŠËî.uiÑïì…ä.I7†¶éåÌ™3g.ä¾Hubø©–ÊÌŒº½›]þÐü:[ÀG©˜»”€ÛÁH¸Hu„Ë+8cUHÅÿÆb†×Ö>9€;¥¤òˆ¥’'Èóy6Û ¦5¼œé_ùά úµöö–°ÝòŠ›ív¥Q”¼aj¯éÏ›ãÅÞC{é¦Fµêp2°ë¼ózöŽd—3ë‡6ðÏ¿³.¸; ‘-ùmF9Ç1#×{gð@FöM5ˆü S¨·² ;a0ŸÏ»ûßÑ4ªÒ Í•3ÔPJ{N¥…Tâ#Þ€N*<>Z›ÇÇ|ÖA|*ƒ5ëì3ºó¸Üè–ϤKåŸöÄ£Ë Ô’W„adµh¬ºy²X[Y’33w«? ,rì‰ \C% ðj'š ºéÀ|xKÎL4˜¯'eóBmàþ¡;²|7îAw´HÛ„,ˆ¿Â–qæXXºöIwLßÀ 6vF4"ð. ÚA6¢€RðšpdSÖÒRþíë7›?kÑüt¥çáZž”wB°ï>Ÿ¤Ñ¬ÓCb´€»J7”RR6âyÑÐÔ6€lLݘ<ñrê嬮iúWä°Žr\ÒÿK_¡KŸOF IÝ)T^RÿÈ•‹#,_î>pa¨»‰ŠãI gÏiļN¤<­lŽWc¹Ý§Š¸É8fçäԊȯ9§ôsòSÛJ¶NMK8Ãfã4bT¼#ü2蘇Þ!t¡ãÓÏöö@ŽÃ`;e\â´¹xµ·«öÔóv‡` þÞC¹1xåšrÜ¡ï8…5\¡k(Û•O‚ä§ º>*ë¬oË"n!7 Ôdh¡FËø2£¸N;ÊÐ E3l*‚öÔ.•·š¸VûইJrJXF¬¼Eæêà¶æV£ÄTõbέ»r=àòÙÊùTPhÇ!‚®ã' gñ}ÏG±—íIÝ<…jx˜iK{%‰{·QÌq¬À·ë©Qû­£Ð6*D€zIõ"Ì­Fc§«{¥G¡ã7cÙ&X }Ž?±o[w–øõ¹)úø¡°=¡) Û6³ÞŽìÔ°~‘Ƴ§¼§üâ\Cü.å¦ÚÒžÌxg¼ž%¾“ÇD’.:é=I?÷–A»Ð쌴xè}'Ä[Bäí"¿Òí¦Ü±jIKɽcŠ|ž6'ã´þ¿Y[÷Ù¸¢8½gKáµvmÆ¥ØÉÊ0jØ©EH˜f´x…v„Þy«~ØÝÇL:ÿ£Õ:(Ô³µŸŠ6Ý‘i#ü ¥éãöÕMo?ŒûŽÿÙi÷Ì^•õhÒÿPKH…IIqõüŸL'invoke/vendor/lexicon/attribute_dict.pyUT HWúWØWMXux ö}A Â0E÷=Å€› =àBЕG 1ýÕ`lÊdªöö&*mŠà_ ÿÏÌãt´a{ê[k¤¬cY® ŠªÑRgˆŽ ¥Ê×TtÅðõ“„‡©IbHÏ-¥ì!F£‰§A'´Ç°cö<ŸZx2¾m<ßèaå’¦ÐÁÌh0¿– ¬ÈÀÃ/xEwízdü#'­?^¾¡†ûózt³7_PKH…II×—¯§"invoke/vendor/lexicon/LICENSEUT HWúWHWúWux öµR½nÛ0Þù‡LI!¸mÆv¢%ÚºBU’²ãQ‘è„€-’œ oß#ãÀN S5Xgòîû;¥þéup\·7pûíû-ü²»,üÐ:;Ìßï!6Œ0ØÑ϶›1¦lçÆip÷ÇÉùš¾ƒãhÁõ0úãÐÚxrïúfx…c/nz?Ä·?Nìà;·smh Ov8¸i²< þÙuTLÍD?–@ö{ÿâúh}ß¹04Æ¡ƒ~0ô|ªFð»w9­ï¨õ8NdbjHfÀlîýs¸zO ÷“kmÁ€:Ü{ 0—¤}÷—""m÷;„¸>B„a¼ !—Ý‘Äý-prÙùöx°ýƒ>áÑÜWZ…§ûÍd×ìÇsìqWqøÂ­Ýä¨AË…Ùp%€êJÉ5f"ƒùL. •ÕVá27Ë"J/3:-Âym$\qM“Wá‚ñr â®RBk pUH`„®xiPè°L‹:Ãr™@) ¸BCmF&‘ô4ÆÎc °*Íé/Ÿcf…,ДkAd*® ¦uÁTµª¤@¶X†:-8®D6#vb±¥ó¢øÔeÐþÁã\H>/‹Lä2C%R윫”’#}Eº)†BÜ 2ÃÕ69ajñ»¦&ºd_ñ%y»þG$´“´Vb4Sºžkƒ¦6–Rf!h¦…Zc*ôO(¤ŽiÕZ$qÃ#1APTtMõ¼ÖCÃÒ¥êÊ ,oÈù†bQ,å4šÅte­RBRmhÈ †ŸÀ&t®B 1)"ДXj.ÚñQ€æÂ#”bYàR”©jd@Ù 7´+Ô¡ßh7œ8ëh9ìˆT±X^|±IÜ$àx¶Æ ûÔL»×xúNbdioqÏØPKãt‹I™XMZc’uinvoke/vendor/six.pyUT úVMXØWMXux ö¬]m“Û6’þ®_“kËR¢ðì±³R™ÔgäX»óv’¼NÎ;Å¢HhÄ E*83ÊÕý÷ën$øª—ñTÊ"€îFw£ ’H¿ßÿ,Ã(”!l™¤ì)…ëøžùIÀ™\y’¥Y,X³Û­\ÁÏ ó‽ë÷û½Þ+vžl¶ix¿’làÙÉ›·o~€~dxü‡·‹KžŠ$î½ê[ž®C!B€ [ñ”/¶ì>õbɃ[¦œ³dÉü•—Þó“ tµeâgÉBzaŒ¢y Üf p@+W$’¥|òRN’yB$~è" ?[óXz{\†Œq Wœõgš£?¤nîE€âb«idO!Œ8 p!ÓÐG”ùQ ¦9 סîÙIà80”vÄÖI.ñ—Óà6Ù" ÅjÄ‚Á™„J•>‘ ÆòŸ0‚G(`à ш ‰ ûÙ b¥V•Àš§U².&D™–YC·œ¸‚TG½þÁ}‰5ȰL¢(yR6!ŽKüDÓ7‡Vo‘¨“ù5â~¼™²3v{6OÎ?_žMÙíçéíÍl "\ðõäúãú_¯çô ulü/(°Ù§³ËKì ÐÎ>æ(%;¿¹ý}:ùõÓœ}º¹¼Cå‡1Hwöár¬:ƒ¡_žM®Fìâìêì×1qÝŽ •Œì˧1VbŸgðßù|rsƒ9¿¹žO¡8‚±Nç9ó—Élˆ½5/Ð'*8©¶‘Ö:®Ä¸ ªZæ-!6QLªd™wêpWáö¬©#¯#ñï@eî¥÷×ö‚ ?­Ø‡² ȹ¤6Œ³ÄÅ?¬t°†ˆ?6ß=/ؼqcqBJ•E !\(&Ñ# -hЯ$‘FE?#Í8$¿ˆ“ð0ר—Ón–˜º@nÿyèŒ,» ƒœ Ö æ=&a 0ÍÕªF²{È5!|UPR¾Ns¢un`q*þÑÁTdr­AÔ¼ãLê °Á=6ÀmiæRé ŸÀ+ÈÈ‚+eOÅdî˜E˜”rî˜?^'qiR3X6¨}èä(åAØk‹UȨp¯ÇlÔ&cW“ŠÀJ±?u8ºEðj ©Ç´â*ÎgP†»ÅÙË•… IóvXî£eØ®®qgBªxÑËH±«RSmÙ]õl«Nh…Ûßsi’-¬“¬›Ç3]Ô¶mì΃0­/'8J\Ÿ¿ö5dëp‰]Áõ]…î{ Ä+a0£ÂÞ=¨î×h¢®gœGÜUG0J(H4³‰ CÀm[0`OÓnsCåc5H”õ®ìh¹³èkhäoêk°ò>UŸ›1ïøhÄž;}ÒÕ¾×í—HÕ„®f§ ˆÌ˜Û‰ŒDÕ þÕ£A§éª> ­i÷2M£Þ= œ¢M«Uyw†²<þ´3C©½ÀÄ¡µå玊EyL™…ÏW@vëÉ•ÊAx509Æ[!,Kr¥oÐ0IÌÍšŸt"A;÷â½Î;…DK±ê˜"Ž©.Ì·ãÛwoNØ2Œ€Dö(ñàn’¥qÙn=¬¡sL… 3ñ|kÊùQmE,­¡ ,}_D» Šî]> 6§…QhµºO…ªLó'O±nÆ0ò¿ÿgõ† ¦5m´94bßÁc„HÂêÞ©ÇЗÓ4Øj©Ï¯…¨ß³¾Ó‡ /Üp^›û‘5U#¸—=îz·eXÒU%Fàjfºk°µ²}ôÅX ­NëþÉ· ]ê…‚3åeÔ<è“G(c‡|Ì%N$ÃNŒ³Û*µ’d9|$´ÏéÃý5z„C˜Æ±Z· ²”µªªSKùň¬9EãN ÏäVZ¹/U°ðoSVæDè*-ç9Lá³McÕnRQKÉsBán<ÿÁ»ï˜:ñoª0dšÙ‡jÉ3Õt¢!â>ý q®÷X!¯ÚÁmB@LSܾî3´…ÅœY‡ÇwÎ{RrŒ´ï´n~mõ WžÈ³Ó–Y£Ô =R7Ë(‘7U:õ1­z¦ÒÍŸY˜â“€ÐV,ª$_(x` Û!Ú9Ž Vƒ­ò:a»]c”À2¢‰$K}ÄÓhN`(òD^×ë¹ù xÚ¼†šÜÖZjÉ '’¯…~[Ë,V’kãê.J9©™ØâV_ižTní¥(œVôטÌs%íû3Úi›Ü`:^*„ þ›× GüË0‚Á½Ù‚ÅÂ"ƒzðfj(hôU'ÖÒÿ­– J»Ø‚Æ›Lª[-›º±M½'7§S­hÐ_ܱ¤gímºÕ¤ ð§¬Ï (Å¿ª6+.»ÙÍüÅÕ¢?õâ{¾C‡Ï9‘ºhƒâÖÖ©œ+ }}OóžÂ:58ñ·£™ß›?7h㫈?»f‰$öM7Ù4¯Xª©…ÛvŸŠ+µp|<½}²8ûÚO¢ˆÓ¶b«¨H~ ‰®a5×{²*ù sQÚƒýùÂ_á¯ÐøÓáâV8WãïJ»hcë–’öH›hý$^†÷|àC‘îœÊ·ªṴ̈٦\«|³uñº‰.X¬ÝûXy7\ã/ü8XÕDîÙz½uå*åùtµ\&h‚XI¹E.yù^ªÄz#°Ù)šw ¥ ¼¬0‹Öuäâ32©y±‚Ê[È0ò''i)æâÓüêò6/û¦}b”ðQ=‰K#W-Mœ|í…‘»aÕ_g`+Ð AP½s5¹_Õ«‘Ü)ÈwàÆIÜ 9Lz‰iGøˆ¬ Ë÷ý+/Ñà»K} ~wÒW»Bõ­ŠïÍ^EMJàÓ)³«/õXîz=ûa S¿gdžÊû0êé«~¿ xH[ÞŒTh›w#óm ÇÞÙW}e¸ƒ›÷2ìáë&êq%Gý‰&n+Uëz=õ|å´¼•T³ºþ°×,µÁúCëé&=ÊPO5 Éµ]*Ú¶7σÊŒÏ^z¥öß­¢,~J¯@hçØìûsÈäåñìJØ?ÅKx£c˜n~RïþHæ?’0>’Õ¦?”W ºäÍâ—ôœÅG÷ïýäQ}6weâpÞ,>²SÍxl·)ÜXãföᬤ]¸II·GòÊÜ–åÌJ ãÞcÔЃÖz}<ب æ2Jü£ÙÂ[=ˆÝÖ÷áÜ)¾™>îô¨»òJÚ§ËËjç"Ò°Ð a'góÒØ)W¯eùÛ±ÌíÈ<‡õ,WçºÍiª]kô»sÅ¥ ô›­¸*Ÿ-­¸TµÿŠûyz9.'É'ù²ÅŠ{$ÇòžÃý xêHÿ£ÈEÞbÃ; ð‘Að|Ћ“{äæ¿ƒ¨N Æ1ü÷\nÒäYoÅÊ=ÕÍÇŒü†”vâë mdÆŸ ¾ô Û§øñÉ‹ƒèȉ@¬)Hœ—⨧!·iâs!Žp?o_"m§¿p ·ž’4¸ºÿ&_B¹Òó5å^´> ól!dêùÆúg™\½t߈¦ë› ™ñ]„øØò[ ðÛ Ñ¿P/埽÷l_Äÿ2ùÏ=Å_ˆñ9¦_ªFŠ›/‹T@•r(ñÇ£V  ò#îÅÙænÈ^««ïÓèÅþöE¸vnÝÅ_‰ÜÅßœ¿¶e<ûe°šû¨Vóvf±mÒšÇæÉck&[Qô®\ÖW³Ùf|Vl’øn~ÀjF«j÷OiAæÑzƒ9é>Zìxý(|ȱøñKxÛ3Ù‚½ÍZtµ¯3(ö#½A1ïp‡÷c|QÖönÐôu—0@»}¢xõíÜÂz°UöŒ¢aç˜".ßÅë>{=Ok1¸ö´¹á8³+ø»-¯C҃Ϟåvû«?Üi‚å™h‡Ûeˆ­ß>‚§Ü“œy5K£WíÅÆóÍÑ6\ðõ?ÐÁ÷ðÍkòÕÞo[#I^|—…š¶^LoÚÔŸûc~µ'ŸÙ‰A>½ŽíÉY¬zŠW9üÞÌy| îb¢ö(O-‚t|&ªßÈÿúš8^Øk7^èa¨K%]ø¯ï4övßÓ¨ÉXq8ª¯Gõˆµ|f€ãñ$küf£0Jc^Ϥ‘DG ‚Ó}Ð'èju©0¥Õ ¥Rï¨ô-ùL½Ôkñ=ÎŽoÓkŸa S½á¬úÒu¿üUT¹³A?meþŠpGìoiŸýѨGCÔ‡ý¥«‹Ÿøn7žÈâÒ•ëö­642Õ†Wئ‰S¢,åª]rvE_¨f¸*·jÇB¨vS*ÓÜGÉ‹4‰. Eñ¥Ryáš.›FMxÙ6»Ü4‚¼Ð6„REËì2.Ù¹=xÁ#¾0áâ[×xø}kû,{zú¯aö¬ùWnS愲 Bé Ì`ØÃ­rÙ2ø^y úJÖ\vôoHð‹×zÇ^¼à¤ã6°ÛÇÔà×¥ÜÊé Íõ9kLGδ¯ÓD}ùcÛ©ùD*‹I®99d +êè5Ù>-m®âÕßêƒn®¨ˆkaa5½éMqèƒMüHÔ{Ã&Å>)&P%o–©= G›uMÂù§£:Ô‚–þ)%¾@ -¨Oõ‘COͺÑSÃé:Ø9Ymñ:VWó=?Òšå°×jÉùù4M3`­XõåRé¦[žWGŸ}êó„¶fbràB›¥¥œC£!èµèûq ¡oÜò°5´¹t°jçP£DÓÄÙìÀVåT±­ƒ *å÷ü¹´*7ýG…–¨®<鯸ÎäË'#ìÙÑ^ýô+ñ´Ù´¨âû’¤^ª2<ÿUzqàE`±˜€€ÌjµÃäôáá¤|y>¤¯ÿýï×#†?ph— ßÛ˜í‹ÜJÕEÕ,±ªƒ¶»Á¢A‚$  þë›;;ç&8XdË [زå×ðnX³Éüãd|Y OÃä_¯:øqø¹‡¶Õ– Ô²ÝÂVMeÙ–;L–ö,“=Ì÷²Ä<Ö-F˜§¢°,¢ÀQš·gÔŽ_d•Úõ†Keú̉ï¼ô^¨…¯ô TŽCR¤5- UîR_ÖpîÌÂØÕÛËúiíÁ^ø3÷]ë¸:µ‡c>{í#ÿSN›5¹ÑKÚˆÉEÃé=ê¼Æ“±Ìyr3ÖXà>ßnàð`uŽ\4m)<ì¤àÈ…uVAÖ«„'ø@íæ€Î0[®>ŽÍ_Ê—Œ)“\ŽçX›ÅŽ}2I¸4ˆÍX¦ú )X pÛ”ÊëLò$pªÈ¥¹¥(QaW$nû)dº½Àr\j.6ì¢Ð9¬ÔÚêãþÔ¿Li˜ž©(2£^åȆÞE†½ìKÍi©¹§ƒBÓâŠG!Ÿª£O†?5ô‡p.î˜4^»*[ûɬÙE]]¶‘UÊj'¶`V‚’fýåErîѱí{ Ö|¨·5Dv ¢è«í’!N‰f)é ûWõµUr8æO?¹¸â(v30sÈÏ~{Og¿8?–q¹Q°³I6tÀ >Eí $™,E o4ugË;;ø¿JàƒÀ“^ekÏC„e}=†D#ëPë†#–‘D%Å ²DðŠMÔYQ(=΄÷”£ã ÙꊛŒH‰J§*ÔÀ’h îƒ`CÔZMb¨Š¯áÛ9–ÇÈdÂv%‚š?zž#,#Bqúª6·œš@KÃ×y~dŽÝWÿ׆~›ªñÇÜèX¢4BY øÿÖ®¦7nˆÞý+ˆ IÀvÑÆi5àê~\ ÃNÒCRòŠk ÖjÉ[7(úß;ïñC¤$o}èìµD‡ÃápHŽßãí_= Îà*{þôà‡üË^•OžLºS ÛP`m †BŠ#1ÀˆŸÝ!Æœ¤å)'IöÊ­û˜©#P{#Nw‡þHŒ`¹ Þ¾àךrÔsÍŸô_Øs)ú÷œ$_Øspú‚žK#¦ÛsîJ@®jÄ V%.úª¤ÊÁ a"Ì\šÅdÁ{ØÉ‘Cvº;ú$“֧”2Ç„ùºÏm§‹‡Ñç™C^WM€¼Ÿ.¾4‹x¢™ëù „ZƒõH‚Ô,%¡03Ù¦êf¦+kͨ贼QrËÀ°àÈ@TK7º‘QíŠGJ¼Ÿ®ÕŒ"ÓÌ3±\Ìc¡>6²gŸèOœÚ†s³~žÛ•wðéÿs%—Q±ÙG—Ñm}èïÇ«.žIyÈ¡¨ahºE $d¨aiß1’bžY0 Ï­'¹4·ãf bÝäM EëÏæÆoá½Íå©+Ú>ÅÏ8…²Ïªî]žÛö߯×WW—×ùúææ×ŸC’—›qÁ¡-‘žh¦ÒÇ«×.o¹»F=¾AÚFçϰ¶tÍeR?®.-[œ‘-öûŒÛ£&ª–¥ÀŸdóS†ìv–;4óÊ,Å7núCCƒÛÁs{¹FW¦P¾ª÷í,¤g0#°ÿÛº¶.æf8£Ët‹{U•º€úˆuÝh…X––§Ïé ë^ë?eC„lTöjצ'²iž<ÄS§(Ǹëò›¯Øl±ôdÀ9ßj±$Æ7†¸|ÊótS÷?°ŸDS’'e6çš’¦‰P¾`xD€‹Æ•#íXªäQã ¹Oø$Yª4[ª¿ÿ‰¢Z"†ù-7ö©Ô›½9–†yåj“uÈPÃãËØ}WÝÉf„¾œ¼ñ7î+`¥…©"ê½¹Äsåi¡’<ç‹YdL4?—{ÐÂ6#®¯@ó¡ul4‰ÐÅÅéÙ‡8Ï“S»®™Ë‘«Ë+h|¨¾¿ýþ;Ö`eVò!Œ€)6ÏêôÝ;uq­‰ÓS®é gpwì1¢оœÈµÕˆ–˦vѽ[yÿ¼×E·¹ç¢ÉÓ瘶¼© LI(Iù©è*Ƭ¼R6$Jòk»ÞÖ9Cr¾™ù8¤w²ÖuɄֵf²ÜÖÚ²ç“ 5ÚúVT¦mu’w¢²ýƪ.„é««šB÷$‹¦Üv'Yv'´‘u•elÉ®~\¼|y5™X½ûyÂàCJùŽ—ªÕÁö,»—v“)¹Â>Òý¨1_r±µì ɾF«~Þ'ÿWFL&…X3“ójj¬¼œ³·´î¥ûš9õgÏžÑ÷52Îþzõî-sòŒWÛêºhrAãT²ºñÐ-]ÇÒOàç›Q_´?÷©„_ä‘ßf4ÞtÖÊàg'…*‚ܰAÊ'+®TgT/WH³­)²|Ë5¼œbú”<`;Ž„¦»y<Ói¼£¦{©sLÏë’^O7Þn ®u uÞ”0-“n’î‚:y­µ€¹«Òb o Ç-„=Ø/ÄÑÒÂ6ºê€P"Ã||Èž J¥P˜ƒXÅáÃÆAp$jÎÇ [ŸììªW‹Ü>v¬Üòó1xÊ@IÌÄq– ‘óá1|-²NœT`á=Jyíeìñ ӛЛZHÄ׃Kïè»ùøáw_Áôwž$qê…ä-qGû8Bk(ˆÉÇÎ')U¸¥/Ú ÚÃ^®e yÀaÀÑ¡#ŠÐ’+ù¯Û©ÖþzáÛ ‹ ¤ÀÝ˫Ʈ_üt5gâËVÉ\ÂÖb¹óÄFQ½|åï1±Ç2ä:˜Ù þi`^`…õhý²¦S¤~­Ô®€Ð°óôÜpHÑË¿¤Lõc¸…‡yŒÿíCÿôTÓ¸L_{Ú}aŸÌõ6´È%À˜—H”Ÿ£Ló˜rxÖ3W-=\DhòƒüÁž?¿½/Ì4I½Kfž—OyðÚñ9²Ïg<¢GJ±89@A—7 e§†¶5p§ãaÝ´Tûη—äÛ4ü’·”àFbrØ”ª|çò\އ>äòH£|YpŠòOÌç8Íû?©<Éç' ñHÅŸpñ'ò7B’øQ[Àcüyuÿ)ôÐ Œèà‘ö0Bc1ñHAðU°Ø&{àE‘E»¼×îAOa‰¸”`„¹»wémŠÉV/{À½* Æ+&ÁKÅ3„ðj‘‘q›@Ém¾³ br#áäVæ(jpË‹G<¹1ò¦øÀ;(äÆ\x~Á«$J‚¸ÅÈ•ÂâLZð6Ë7\óÜÇ2 9„ EßÙ¿8DG[¥3”¢£¶Ünz¢Ø4g·2”-g{†FÀð¤ÂÐDê-Ü0‘3û•&Ü”³[±C×p‹J¥ñ:×`Z„:‡Û{•ÿ;‚¿­Dˆvò½Á=ÖžŽyà :èHGÌóÒN7ªNªÓpø*×ÁÛßë/ÂvÝT¹Cqå9þF‡íoýO„ Î!dã-¡GÙóQçÞМw;ÞóÙtJ¨™d_8/¯å—9ôž)½8s¸Å¿ÈKŒ%¥  Q3„àšIµ©ö1 ádír÷¶þ94ÑpÍ­½¤7΄¹ã ˜rä¸5dv·¥m&6ŸÂñ¾7`¸pÝʤQìDk¹Ÿk$„«Ø†¬ÒN@µ%ÎEšÄôÇ~,DG¤ và84ƒ@ߣ2è|,L]4ê #‹ÌjšÕE@>7²­ôGIáŽQEKÉœ¢¿ûÄwesX*AP[6u1í]4Ë’3ªðG:ø3*=]‘9{gQý‡Ä¨4iÜùW™$ ™Û^E¶ÍßÔc€´YôPKH…IIÖ#<9invoke/vendor/yaml2/composer.pyUT HWúWVMXux öÕWmkãFþî_±u¶[i¿)¦„‹9 u —܇‚ØH£Xiå®V¹„ÒÿÞÙ­v¥•ÏNîà*plÉóòÌ3ÏŒ7“8¦EÇdEnfïªrWÕÀg±Ÿ×œW|v;™d¼* È;’ãW\ åþu¾ùC‹G`¢nM~ÔY•‚ól’´®‰—bÞ‹¶8›¼vhØ·ŸWwŸ h¡LRÈHç,q<¯¡ÈŒ«¼äí’²d[ñKüçßÎ%ÙBòKd}§rÁ«[ W×Öç›7W×ç®uiKk–g:¼¤¾œ_ ´¼”‹µ|àDµpîAëÅÄÉù{&3r _e…Ò*iJÅ&}¤yAï øÍ:p gȬC±f©ÆÐÕ,S‡+~BÌ+ (MH¥ðOÂ"ñŠ?$µ_¾Á¬t3ã6ô¼³ÎÙ}¯éÏ>²”µ¥ÉŠdÄZ@òZÕåN¼X}Z–µœ¢¸Äµ‘é]#HV5 £b-8'Ön$ŒCÃ#³¯ŸõåÅáê1*nó;¥ßÀqÝ^üùîãf}yýråzÃÚ¹ªÑí JÍÔk¤TfÂ":Š‹á~í±$s²Sá‚ç3Bi¤ðäÚ®çENë‹UÃD+]¾õ,0§1’ó’3¯¼³ÌBêïhFÑjÁâ ²œÉøÉ)Ÿ+¯S` ,‘Í‘½y;[D={PœÍjðÞè÷[kç‘´x°´Ÿ¡Ž³e•g#¾ˆ<·0E³ÞšþožFòÓŠüÒ 0Šù‹âPY[ÙËqnìÃÃëmªïW¯f -WÇïÿ«Ö=Ú4õHóäžý¾OL ª€2Fóà±›YÚ}§,Ð:æÌ…%ÃÐYI—¦“aøß–ä4kË0r{ N^ ãXFžª…{Oùm¡= ºšoZ ÙÀ.ä–żõŠgI|ÓmðPKH…IIÜ‚yŠü9b"invoke/vendor/yaml2/constructor.pyUT HWúWVMXux öíkÛÆñûý Æ®Aé¬SÎna$‚ÏF$E€Æ0h«¨ O\ݱ¦H™¤|V“ü÷ÎÌ>¸OŠ’ÎF‚VptÔrvž»3³³Kæ,IÒ¢H’è*šÇIöUU6m½]¶UO¢øûte7?Ï"øèM_×5´/ÎÎVuµŽþŠòõ¦ªÛ蜷•UÆš®íL\eiËÚ|ÍTÃu^¦Í2Ï'QÍ&Q³k&Q»Û°æìlY¤MÙDGߥõ[–ýãÏßý~gÄÜ`eKÀQuýo¶l wéºH–Ýí”òó¯Ý­õ¶hsAdl%I^æm’ŒV¬yüàÏ©êDz„ÓÕð+¨š-·u“¿g=0M ªJnXÉêTp1_˜ c›ŽSø&-Öqº¼eË· ¨<µy}}»ŠÚ[V³(…ÿÖ|eÕr»f%0“¾Oó"½.ØKÕ¡fí¶.…Œ„ <wÄnX ¥l¥e&1ñ¨dZEvªzä+—N‡ÏáFbO$&â`Šüð¾“M^Þ,Àë×e³]´·iK,j–®# Ò¦y ª‰xwÅ6ÉÔÙ ×ä@ê`Å ,§IK€yÛèUU²ƒ„ÅÎcÛNˆE~Mˆ°&>ªD2ÜõáCÔ¢sw›ƒ¼ÕäÞ3Œ½½ÌNû'~VàuÔý(/b&+²K¶]¯w®]8üO1Xº¿Ù˜º–Ñ >3 “tFœDˆðŠhUcË>ÿÆZlª “pÂ-RŸëmñþ¨y=âî3ÏüX‚!\h€î¿O‹­7–S~`êåÓJØý܇^ö¡™¹]°„•@&”€_cïŒ#(œq~> ßv¼~Ã^öÔ+¸»WíJœÆUö–íNˆÔ•È<¸Ó )Ào®€ÚzçúµÛ´¹Å®&(û°d›6BNœ`‹Û9 î|å¨8DÑ•bÝÛ¯L²‹ÌÙÓ%òC ;ª`ô¨£5€©‰Ò¡Wóø!=ÕÖ)>¨8Áù(¡çæžCé€Ï°=ã}“æuó?4ÚI^s%ÿÉÇúÉ6'!d|Gz‚í±ã3 ²³{šez¶¨¦ž9ý™Õ«Gco©†˜ÈC{ÉŠ‡–gS>ÆNN¸f›"…±'x%Öj§ùM PéÃó”ËÎøøÂšÚ¢Ç…¿øãø±ÝÅàeþd¶pº¥K«›Ðe—ç‘Kâi_ñåu8Ьž£J9á§³Å$z:Þ‹îÃèž<óᓺ Hd"šD_X8âY¬2 kÿ ¿Éi|ŽÝ7Àô˜b^©.ÓfSäí°ŒžÞØz Î&²®´Ü¥ÑJ5Ä…$9R×3ó®à™ àÑ;0Dóü*zvT“Yrƒ€W¥Âæå*‘_ú`‰ùs„oÿó⟿ð‹Çû‹ùÅãÅX4ì—pͰ+Íø%þ{ù0žæþ=<Ô08}­Óvy+aíá5¥»#ß*^-dzSWÛ Š5§†ã.Â*B3±)Ö BCÍ‚¡6Æ™-:€(É»h‡xáõ òüô/FÈÍ„ó0A*FDaÑäX5ÖÉ€6ïÔ¨ƒñÑhñFLD#÷’± zÈû¶\Z?t>{fÆž aÁT£çÑ3Ï)‰ö1eµ!’(“ÂÔÙ‹tÞÄ>'sÇEÌ KU¢5¶f±šA.¸´T.Í^’35 ð‹GH¤¹Ô&ºÔÀ/Ó2Ž4ÞI§zAÚ°6y‘ΰœD:O1¤&‘«y:Â4<‡.®"“üž#+ägªup1Áº-›Ewx”›f7ǃ ýY‚4Xj&Ñ5[¦[—y7ú* ÉW¯¸ÀRX‰çrº£ßH×,ïrVdÔnÏú{8"â-í•0~2VC´¢Cë{¾Ã$á½ßð>¯UUÅ®„tEØÊPEκŸpúO\½håê Ò}eìœXµg\ó=ù hA<ü •‘·lmh!£&Ò…+Äñ: l‚èa½f÷Ù{Dà¸sf/œÒþ³!§dŸùðx%zô$]£ AÒ´‰~ò8¶Ÿ:ã9VÁ?€ ­èqˆÖÿ°£9H~[¿wÿr’ðÿw+N¯ž3F!¿Ò°}Ïu‹òÆIã§þ-ì9…%®Cö”Kd¹ï£ÔJxÁŠ•¼LB¥·JâE¸zôû®W¿Žó5q£¾Ä–®-¯ž©Ùm<¥ã.Ã\ —’sB?ð|=*·Ç ¦ñ$zÒ·"ÐA_X«\o󅥊Y+th “úO04‡˜dÏÐÔ6BŽ+cŒjXöè“«CcXï„-“cUðˆÞ>ƒcP)ƒô qµeê—ÊÐ-IB³ù†µA ô„ mvïW”‘Ûˆ§«ù:¼vŒ*çAœLÔŠ†d³:Ô«û`©PÏ+\G©)Ñ\M`DÓß”TTú=™Aª³×<¹¥o˜(3ñ¾8i"<ç(ÑuŽ}L†Óú¦¹âU—·w™¼,Ù=À‹}fëq1Š8­GÁ Å¬ò- õ>­áS§CÌÒ+Ì´þæn#sE¡´ñ8Š~tL†}¥dê‡Ë»D®†´@j¿ÄC€uï=á…C_±VÂjqy¿HþƒÒVir£8ðç%é‹ü‚‘ÕG .XÛc+¡âŸa²q ‹æ ‘q®ê­„jú;¨¢YT­¬jjÃKð oš!Є¯$ƤdÚ!üx|ä©çå(¼‹N‚þÚT¼’ò’©-¥%©·®ªÃÒÀ v{’¯žþâ ׉ö—êESöû ±ˆÏ<.O4}±Á(¦bÃÃ蛪^§­ÞEŸ}ÆQ|ÎQÌDA¹æÏÑt:uk¼êLlÏK¿'Ô} ôpf)×ð))^·w«ÃKùæcùü<Á™fϱ½H€`±óÅ€‡d•Ï ­º?‚eCú±‚bF4'ÃÁµxG'þZpät…©a°ÝUuÖXÖ~(5<3„€ÏðѨ–DUƒ‡(Î=Ì)«‡4nÔßâ+_Wƒõ´Oè7wdùjÅjzéÍ5kï+ÈÐ[y”­¡Ë›è¶º££2t–5%eq<Ì7?ø™1˜î9„ÝyrÎBä}Õ™à–›èSÉjî¬é<ØÜÙÎè寋[‰Af… <#„EÌ(ÆXaÏ^©4@lÀŸ{Õ ©Éª ¯«fºR´.ªÙ¢r„Cåy '‹‘¤yסÞNÃÖÍnâ v¨˜y6SÍv_|äOrx!tòU9 ”ì4_…’½žTÖ/ýua¯»º³½å}ïö ÷6%, D‰ÿNæ)„´Ìý˜§êödütº] ‰ŠÿbôD‚ÝÎìÞÙSÈàv¡îDRúÞï€=ßSH‰=Œ!‚ ÐSÕ~j¿h|“î:´â"_F =ã~¡øN MÇ}©Ò^ç„uñ!¢‰ÒâéùÒdMz/TźhUñ ¸?ª<=€6H÷ÉD³CèÓ2äì¿PKH…IIrmÕìÃÚ invoke/vendor/yaml2/cyaml.pyUT HWúWVMXux öíVMO„0½ó+zÛÕ€É^DoÆ=ÓT´±´¤-®ë¯·°-ý£&º103}ÓÎ{Ì,›aLÃmÐݪ8# .©@®r´*nIºÎ̽ö ç]Ó kÍû,«¥hÞ‘†!Ú´BjT\©@樸h¨Ö -¨\iÙ•ZH=¶KN £o¬ Z ¸†Iމ öÅKF”B×õXK+|ûÀÝâè4˜WP#Œ)§ãµVçÈ$i À)cw<™Ç°ä¼Ã\)ÆÑñïÊÓéc_¤’œõ•Ïh¤Uþ”îgaízÚ5ÐØžßì(?jF:¦±Ò;›+Á!c5Ûp!J, œ–„Ù$Ê+Su¶´ÒO3IægÁlÙ™€É^Œ^=¸Ç!ØN׉ ®úôÒZ(çÊæ†/óe”2Î)%'r9<2 O]Ó‚:äpfu 9é-Ëj% Š`ÓJeÈÝhÕ qüM ZDšbÍ’…æ¬äLk2s|›ä<Õ/ÿë\ÖÆjùC–{*¨ ¥(ÐPzÔÀë‚h£€5Åàödž±ŽªÍ‡Ó‹PL¶šË[èˆK&¤À’q—„¢²Ó8冕ùXI²€Û’Í“•ïÆQ}·“}®$€°‘(®.¾[Ž%öã2e–FÕJ ‹‰F)\°aW=ˆ'á@¾_‡+øé$Åúã¾?>€áNcc"-‚c󩯼Ëq'¨¼6^À« C%­äAu¯Ãµ¿æQƒ]Îo]¤¥½VÖ15…mG"Ä=sO®™Ûëäêý¹vbíÄÚ±+º!rýG¬T;©6Lª­êPKH…IIúVؼ˜"©invoke/vendor/yaml2/emitter.pyUT HWúWVMXux öí=iwÛF’ßõ+ÚÖ* -JCiw6ŽÖ²G±©¼¼g{¼–ó2»’‘ˆ4h[“dûVõ…>ªPWœÄø Ý]]]wWX[g£yZUÉ‚%/“IU²ä}’ïâ,¹Jó VÍv^dYñÿ»XÄóy¼Ø[[geµHâ9ÛÛÛgGïÞŽ^m½;xûŽM‹Ér ©×£×/ ºzͼøûóï^^¿“MòbšÔïD}þŽ~ðòà-û…•É?—I>IàÏy|y è ê%¯:úïïF¯Ÿ °ê—®lÊë¿:xóæÛ×ßÈê=Þ%þè?ÒEØhm<޳lëìØ)i’ÊY±Ì¦l¿O¸ùš'Õ¬˜–쇋´J~`q>e—EY¦gÙûá<[–³¶‘ö%H³£Q(¡q±³„ïAåS;»²,¡2Qíöbo”&8-Y =ÆàOf(ÙÒj&^¿‹sQZ²ª€áåÓ ìURš2ÂíòrR-Ié‰7#vJàX9šÜ ŒÅ¸áW¼¨LTŸ/ ´âÜ”qJ"…Å`‰—‰;jaòüNEŸÈljìDHJ\¥EÎ2hé5=.Éû´X–²®;fù–è_”‚à‰ÞXÜd4öÙÐ"J‘WÉG¯óEQTã‰(ƒ&‡qV:æIy¯æZÒeµ€¯&ã§äÊ«gb:‹ñd--«tR"‘œ`8€¥\ §l¢êì Ù–æ ¨NŠ,Ù¶‹AxSöaJV^Æ“äY!—¬B¦ê®ÌšŒõLÆsPàm·Ðå>ƒà^£½¨ÿÌ¿ôCûí¤È–óÜ_# eïË„ì]–ø}?sÊŠtÓAÍ™‚Êáð@ƒ²tCÕ¥ì_U8®­¸Lòq]M)V‹y\Uh1¦I§™+^Ú®Bký7å,¤ÕîBþoW=;2ÖJ±« ÓsÉ/®{;ì‰ú÷ ÛÖ€#þ zâv*<š=‰·Ø‘øë©óÑn¨O‘ÿ&z¬½TZF'ydöl”‚Ï?†r ñx5þkœ†º¶@×ÿXf-¾@[už~ôì2¸Ø±*"ìÑ›Er/@Fâ|2Ùç&0¾p€\ÊZcYK¡ë@s¢#]|^òŽÊê*sMº®@õÁXÀ12˜¦%X„}Ó¬¼MʤRœü"=[¢Ÿê›d `„­¶€@ÉmeÙ_Ñ­Ù¨ •“ wAn0*Ö6˜\ÐÌž¨¢k€Ù@—[T¢nž1çÅ"‹V½>!!ÊÑ™Ð/‹ËÞ°ï×å(÷ˆß[®³oÁ6sð–q™”IA°ƒÕ9GaçÉ >VjÎq–ÀûDØx0)Û5A¼A8LåÐ5ìA.6rÛˆc>žZ6"c-ðSÐxÀ^Hky„!ÇÈaÑQMy‰ïNM¯$#AI7»2èÝVЯ„o^ò¿›Ë„ld¸Í(ƒGYK«Ã:8QoP3ÒÜâÆÎžcÍÈÑõ|¦ ÚÉ2Á;£v†]c³¹Ïv¬¢!uW£|êu¤Þ…»ÙêØÍk5¼¸}¶µã’G”€àŒÛÁÕÌMó V&ÒÍIcȹϡªiS–”¥xã.KËe¼ë›Šg¿`ÃÑ x‚€Ýúƒ´Ãf×1;ÄwEÚ0´U mL=DŽʆ›D×Ò qO°]ÿËçpbž´( “GÌq«gOÍ>%AA…W$æµëI:Sâ„ràãzÆ,ÂZU b 9³%igL!‰>|’ñù®=ÌÓq¦çéÈ«X9 sw§ebç± H·¸0pð숱Q>ôŠÏ†1¼¾Ç6 #L/\Ž5â ÛX]Sݬk7Ô 7qÈIJ²ß->bX~ÏÇ¢Õ)ÙÈ#ʱøãàˆvd3QDr‰âžøåªÆ6A÷Ä/‚!Ø¢¹ÙÃ͹Lì3¡åÚú›V[e ¬2o>¥´Dr¨Ìd–L~óìº6;½¾'žÜ=Ëqµi¾¯Æ~m¶¶¶‚ÖA©…¦ -žm½½œ6³˜© Fú¾ó„ Ý{¶‘¾«Çø¯# ¤c¼®o§æ7óîš'*ˆ]É×8³š›QªyÜãk’{+ ï‚3ÖuKìC•rùgäk¬i Áz'É£¥ušô²7­CÌ™·eú""@ÕŸæ«nÈV×ø'Tr"Èk:€Ý@[BIb&•[¯©ÐºNÝÛÁ6¯*R¨Œê‡cøn%Nn’¯èF7ðêÓÓwŠX€i¢^[Îìþ‰'u¬3ítý‘·²¢ñF»ç[í -tÚ÷¬%噢ªëó°ºIÏSÀ§¢±»‡Ž9÷”þQäÔ°<žt˜5÷Ç#•—r5nR§Ól7m¡ãÙ"å¥Òl™Up–KËzÇRC‚ÁÒOjd~ r,#±KÕP(+5»ÁÂ)# õ1 2¤OüŠ+¤àd³¦h¯ ×(¢x‰ä½OÞ"h ×,APÕ|E÷æ:µ¡kØæZ(_ã\}Öù,¡V¢¬˜JFPtëwÃâóÌ€ h)èÌŒ»—«Õÿ£H¡´µj³g‹©Uޏ›¥ýù²´jº€1ƒ5ªuWA¦ý–Fö×ß¡‘5×Ëë~t]„õTŠ9»hˆb©„ô>΂ÙSYÔ$W°1¯õ³ ‹x>§ž5'@:æ:£øO^ä?%§sÛzöé9ÏZyýÁÜVšôéèv÷|áYe@͆œâ ¨“8ßT„›Ä·¡€áȱξæg:L_ÄK*½É×Çx×nÒÓZ Ò{q½i9Aù{eê?;GV6þM“X¢f‡T«¦Ý¡^¶ õê¥ÇûšcÝÜUtöX[:×òAË {­¹†ÐŒöɆö£Ì^X¨W”Þö‰W¯³äšmÚåÖíá¦R{Û1Ú]­v7+l"~bqE³ö­<ºÛ 4»³HÓôk‡Aš±^)Vóënˆ@l£H®^Ñ6¨×}®ŽéÅv[Oš‚¦·¨N±Oc°â-ûK_7Å×5‡tõÖ?på[{:o!ÄÀ¶Ëy{/tË‚jÄ".'µBEÒLo‚RµŒ·z‡VýŠ«ÛÇ£A‘GCÂ;ôË’ü‚;²Î1È©×ë\®oÛ+hä¦l^Û=æC.Ñi­ô™¯Õꛇ†må7÷™ÞÈîÀíwz—ÝÔª­C'ÉÎŽD2˜ßF!q$Ê"î5°»îN«òu¶(¨±èÃVÍã0ÎdÕÿÿ+1G· 9oˆ‚'Ïæ÷=M•mž°ÝÇœI!‹gì3±:q Zuƒ*Þp•Ph¤Q7Ðeúz #Ns7R4oèkßvÀµ`€gü¼y’N.+–×s–¾ED¬}õµ–äºîYFÁ;¯ƒN6åvìI ËÉCý×&€Ì„(âqBé¹A{Ãhå¿uÛSšuY¡”¢W¥Òc±»ÅŸkôzDnFlØ?îq¼þz=³Ç}E®ÁUž¼5­‹ø .€4Ú9íSǸ(sLúvGŠMêz(«Ã²aŠ‹ŽðÖ‹Õ1"×—:²‚BøöˆxGMmà1ܪ޽ó°AmƒÀ;9U×9z:دM³ pO<‹ÐÎW-?:îà…oæmá­Uñaäïx##m¨é†óÀ°VÓ,ÕÙßeJY|œ°Aøb…?á†}cƒ^ÝX5ÓxkÁë{Ì ’¦CpÆÝ>¤qR”޹æYÚzhöËÓ¨ÁN8øé÷>õƒÁPpLᱸˆ¶Š½Ù²–Ó“ˆ…}CRHÚ¤/,Qa¶Œ3”Ƌ譄÷iâƒUcƒTœAj¨ºöº;Jãv-Nu™S´uS^,i©•à$:yY—?°æ\–MaƒžÁº1‹œÃ ÄœÍìNž0]Xò{ý.\[`ôp^dÓèf˜¿„a>É"ÎZ6¤¸¹]ì6`W º\‚(®&Î<É?¾9P= 1šÇ?bü?OsâË ¦ñ”î´B˼\^âí|`¡ð&>omL·7¦¸·ºgöØ÷fÆËˆ×Œ¼šþÐŒóŒbtòT£—ï»…qòHç|YV¼íY"®Î³Ã9Q #ä"úeú4èñÖŽ~»z—ü0’H«ñ[|ªC8lc:M$UŠcìI/ZVç[£¾AYÜF>™Õ§TwöGÏépϱŒ†{²õŸà¾â#[FÖÛÿ•ocëí¿¢Ð¹À2ÚG”§!H“æ`äSã>- ÂÀÛnj²…I#É3™¹¤Q‚Ò@2)Œòœ-)yò¬%¢E½«JÜ΃oŸðL–7(´‘€¡ê©+E7 ô_yö`ïo_ìoþÛ`¼ýN¢^ÿø4ò… GB\ŸB­Ó¥ç’ŽO°}E_­îHðV{HbÝÍâ üÜÜñêLã*ÆûÇä{$k³!B³I‚7 Ÿ]Yw0×#à5°Êøìjìßl€»ÐÅ š «pqý¡°"Oì0ãdÍ^~¼s*LèÉTxåîÉÇÇ=YîwóŸ_E–áUáúþï_i‘ÜÁójŽ|tƇ(Ìd~$‚Dñ^†‰Ê®šda¢ ¯|ºf•+IEOG ©TÙÿ>uS%pWè €·n4AlååÖǧ?ÿúÅ£¿<ÅÄîÆß~ˆöiâLUí¢Ïöˆ‰óŠÝâ—¤øÒWÇ”‡Ñ["Œ^¥§[&‹¯³#¼EØh:‰sšA< ÆTÊáÇ%ûdY£t ž¡tÜŒ3é~7<^<˜Ú;crÐHðåáúJÃÅ%u'|HÝ ff؉dx#PÑ·"ÔI.ì“»v<~òñË@㪢n øèöNûå‹/ýÝê­–£áÐmwxxø"›3àåùrÀhÆIÚ±S“ÖÛÒÌkøÃHgd ·Îa8Bó"©’I%¿MÔ)Ô“£RLa™Zœ>nÀR1 Çðž[;4L/ µ=9 ÌŽ+IH^tÑ\‹Ž_ñák{+éÝ VÝÞ*…Û€Ú¤ ɨw£°O;«Z­)×ï~øè6pŒSy~ ¿Ö?G8ͽ¬Y8Vïiéhˆ‘MPáÀœ÷½¹ÃžZ§ÂsÎ$#,Ý\1P™TQɦ`‰Àã|˜Å•ø®„ }ß§!4 ÕÛ“ÂÚe¥·£üR!gâ ,9K§øý7dÂlÀÈ ö[±ò0GQÄãàÎ"‚Ê¢KòÖQy2W]µª³¹‹ü6^>×ûVe ‰™êmùšeÝÈÎùR_¹ 4´S­_ÂtÐ4ÔGJ¾çÚ`®öw(Îâ’ø®oäΦŒ*ââaóREâ“Ng9öõß_ñI Sló÷9š_¾°Þ˜IR¾:±óŸM(rŒzz.¦Ö5,ýЈK¤åž[ëÂe§±½ÍÜ8ž3á©ý¼ÍYã>0ë" Õ«/¸LÆæÃ§›bÏ’ÍÎ0x>àh¬Š >Í‚†x¶°­£À‡ÙØüèñ•ó”L•å‚^îá$bëvJ|þŒªÇëøc¬^‘´ÕýÄ:Bh¡QÓg—âtz¬N‡®ã7å¦ý¬Bú.Þ\yå^½Æçw¢t¯aáʹkÌÝ)Ü)˜›çFGÏÞŒÆoGo^<½½~wÿ¬áàŠD´§þF³äãðKQ†Û:’ǪäÌ-ùJ•TnÉ*ÉÝ’¯UÉ{·ä¹*9wK^¨’…S²£¡%vÉÃz¤ð·Utb8ðÿUÁ{í” UÉØ.áœÄ²eôÒ/ùJ”¼‘%¿ºQ£4ó:QãC?hüÃ@Ú°?<9q5Bf³ÂÛ‰åfezDÐÒé„‚µÅ 5$ì]c#…ÑeㆠêØF‡µ‚³ZÉQuwR×sP·J<Û ´QMè' dëø¸Ø 5?žÌh¢«½ R*ƒÛdœž>:G$:ÀFAê}¹1üÐ;ÌOÚwÃÇ­Ðþ·éÅC¡š¦-ÜÚ¶¢x®“CyÊÁ4Ÿó2µÙƒÚ[¼i¿i> É«°ÉeøZf¦I;³ö7ô:LTüÄ$½ÉÅËPÒÕ¨ye(§aš?†u`:,FEÓ¤Â8ûä‹’³4¯J#.2ü%/G½ô;Ixÿä%rcxžJ§'9x‡Œzµ" ðnãà«%"ꎢ-[¸¥×&Fýìž^¯Mïº^è¦ÆuAÊsŠÐ³3¸xmòÚnðŠK¶øˆ¹'µg“ºÄÃZ%ó× ’#+Û®våO0оó¤6²I(7™µW˜ª‡ï»³\œËïÆI;>ðä]ÇøÙ|Í<_ë Àmçü?géï3Kã4îŸwŠk‘év,]s:·Q㯑¬ä=¶fBu@èÄæõ[wdüò›Ÿñ|ö¥àûX3» [ûÙ|Þ¢ù ÆúRÁÆÕÅ‹EQTêÖÊ•,˜º¡Ékç_™Lì· î¨ôQhsÎ÷µëÍ*oÈ÷´äyþè[rînÎÍmc]·=]gÕoOÛáó9ò÷ê^Îç9ŸäŽœ´gÍù²x䌵ÆHÁ@áI‰Ux¨ãc¿Jb<$"œû—ØNÒ nŒÄ6w;ö%Œ¬C¦`xrÅâ¢/Úà¢,H6%>#úÂÑ‚’ÉYâeb»I¼ŒœkmtÉ+ã_k­I–Æâ¶`‹ØÌ?ζš5xìh»áÝaMw¢é˜ÉÄN¶[Ž­W|yÝÉ38ˆe޽r+™ô£|Ç»UK·f²éY“qÃT5MÜ+ãU­YXQMsT÷È…õšèº5 Uæ<ÁgyÔ%›åqžå³<æŽÎ2i,z-炇gägßÊå[OÒ„œäòNò-2íØ™ªnß¡çûF.=:{–(þPKH…IIÞœ invoke/vendor/yaml2/events.pyUT HWúWVMXux ö½UÍnœ0¾ó–¢ ¡}€J{ˆÚzH.{\E–†cÛl“·ï›¿B»Y•„ão~¿ñÝÛ£±šå–ä‚fE­DîÎ m¢ŽÏÛô[Dð) $”rÉ-¥‰QfÄX¦-­˜~Ù=( Y Û çßh²©NA„tâÈ»†Zï#ëÌZ̓Z‡x'¥ÒÄ­\’CÌdþ¤tœ‘ز“[xU žsëä3 Ľ­îá%ybÆY¹¢¹t€1}j*,‘óèÌlŸ—É!Þ˜ÝFÇdCÄgäöéÌÓ(Ö!‘ǧÁ6Z4lLÚÚn+EiK¥(IV¥ÙVÚù  ðd¶ï¿sé«t-§^ ‹à…•±WB`ÿq%÷NÙ§Ñ't9¤;#Ù—2Ë&Ä”Bý¢Æ¾ ¸>mt‹'øž~îÁ³N\ñ~ô€!r„ ›yMïd1oŒ1QtC~b„à:‰9è0öV«Ft\ âBÍq›«‚ËÓúcð.Z€§Ùü£öCåí­Z-íI«Á›o†€<ƒ6Xò°Ã.2ŸY›·¾';q Ñàyf­n|¯ÏеXÕ5Kõu ™Ý ÎÌòwË>g‚éÿVíišý¥»ôÅãªÐ5†[?ƒŠnˆMæ×^¼Æ0º‹K¿‰%^‚fߘóQ¸ uÏê'Æõî‚âG½ýPKH…II+»•Ülinvoke/vendor/yaml2/loader.pyUT HWúWVMXux öÝ‘M‚0…÷=Åì@Óx7²uadi̤6!)”´Õó ´È¿’¸Ò®˜7_;o‘I‰¸GføI±”ë€B3Ñ«üסUš×%dy©´…­MŠb¢–L›‰˜¨ªœ“ cõ=±jÜÑÜ(ùè_ ‰dÆ@g:¼4¦(ÄÎ…s3šBä§Ñ†Žº!N¸ø·7{B :)€˜™E —‚Bu³¼À7k7½(ïdˆumço©Ûº^êvy‡µŽŸa÷«×dXÓƒ 4¿ÑK؇ìÖçö™}“×PKH…IIµ ‚¬‡ invoke/vendor/yaml2/nodes.pyUT HWúWVMXux öÍTMO„0½ó+&Ù ²{0›Ù‹g÷âј¦.æZ ¶E³ÿÞ¶|v7«&&Ú´Ì{o†ékƒ§JÁ¶.0®Ÿ_p§“›Ì(°B˜`šX!/SÐtŸÂ;å-¦ 4•šTT¾¦€¢p³ži‡%d¹eÍ?;pïyÈ«š¸_ÌAC:¦“Š%6²¯xRÏÓ0†¬¦˜0éÄãþÿ8SzÂ_؇r"<‡å`š)ºÅªÑ‡M4‡ ?QY]TYÓXÊ(¼H ÇS›ÂI>O9£0¯lëë )lŸ;èãÍzùtÕFeDÉ õ*ÉÉ^‹IÔ­…*6¾ÊCÙ2UbÿÔí{Fˆs5!f&h…„¤£#{Bóv”Séüoýž³ÂvQ¹Xôó0h‡7n¾­úÒ/•>ptó?>1S‘0ï¡3w5çæ>`µ8îÎ/ö¢äõù ñeˆ_Œ¦Á·Ímá3ïÓÜ@=.ˆ÷´i˜ØÅ«:˜¡}PKH…II·6ǶÆcinvoke/vendor/yaml2/parser.pyUT HWúWVMXux öåkoÓÈö{ÅÐ %éºaÙOWÕv«l ,¢-ܶp… &™´Þ:vÖv€hé¿g^öŒçá± {¯%hlÏœ9ï—ÇÞÙCW7ÍÓ8N?GÉ5z;:;E×Y¸X„ŠrtzÚ<@a2#'Ë0Ëñ }\£exºÊòèF3œOqRìì±ûÙpg~çE†Ã’ŽÃÃ#tyu1\^.®P´XÆÑ4*&³tºZˆc„¿Ô.í‹)ãó'U›B¡~ŒÓéí$Ig=yyòúl|~EÆïà ðäùÅøäêù›ñ~5áT:Ö`U÷&i6‰’€‹qžOrü× 'SL`Ã8ó1:}>º´ÞýŠ–YºÄYáõÙJÓ4)Æ_‘´»%V; *Pã¬Ðše1¶ EA¢ ¶sÐHeá¶+S ËãˆW¤u¯FÏÐèüä—Ç0…ý"K i‰âjãi¥Iµ’tåòdt:ºÔÊ ( Ç ¼´h©‚«‹p¹ë®Ö©fUëH³èy5©•OúýôåÉ‹ƒËñ¿_ÏOÆÜ~úì*XÍÅ[Ù˜ûHÜ¡vl0Õ à§ŽR9½zõüüÙ„âaw¿ÿbü¶Ù’†Õ3:}=ö‚1Ø·B‘¹¡²_õôôåjìµã¤€˜*Ùš Œtàa˜h']EÉ€<_YÒ*jÁ_ázy~,xX]¡ü–Õ©|àÒõb‡Ðš ʼ&|$¨Ëvä{úüâò å¸È¥HzˆþV£ç)º‘Qep«Ç¶;S=dëõ+u(nŸ@¤NWrÜ ™=€büšA• b¥–쨮¼­X¸<Å9˜ÛFš£tWsâd©öê^½ÂwºoÅæè­‹ßÕ]q5ÒÓá']‚Þ¶°äàrgŽCjÀfüT|£mî]Í”ãà1G³3!~©ŽÄ–ïL&aO&è½ë½¢eB/@ü×8ËÒ¬÷~ggž¥ „ÉñSiV ³0»Å3R”ÐAlD‘Þâ$Cöù´O€qýb> “gÕÕiæ9’Öí×–îß½„aêè~úñOP~]F$X΢‚D+© :`Ñ,„LŽUD¤v ‰£ë›bžfŸÃl†Š0¿ ÐgÌáÍR”¤º&%Õ"LÖhš.”¦œááödütôúôjr¸^þ½#‚Òª÷ wÈÿòUzyÕ+ÂëÃu¸ˆ‡ivüòóÏ¿òaw ð ÏѬ"äŸãxÎé$9q`3¡|†¥ÏÓ«#øÉ' <€¼Ò4p˜Ü@) æFp¿SïæEXÐïÞ«7 ½5]§à:=¡Œž°P ¬¨èšEù2Íq¬=t!Ê¢ `+,Š,ú¸"hô‹Mc E0™uá9ž<±ØQ6”¨Lo08CÊCŠN€ö§7iDàʈa±b½Ä(Óß þR0=–c£¹I< odÝ ¤<”b¦Þ²J¹šÐ4¯Iô׸.¹ÁéÔÎp±Êt•­°rì„O7mŸ࣡"çþÔÐTÆSŒgh¾Êànöc)üÆ«ÒúUÀ·ÄÙDaíp?\5®6oX1ˆ-›VlÒöW•k>Rˆ^’= vÜ'RFЕ¬ið|':D/H‚Œ¾¤Ó/Éì1U3:pHÁMˆO¬!„Jv®i8ÜtÑõH d§ß ]¸ÒWª(CU ¡BŠ5$ݨYs²•aR*Só!’©+ Œ§O¢ŒdŸð9 Ð>“ò”]ÓXƸ>Nfô~Íç¢0½$gÊEØÔÿÕ¥MÁ–"…¡Bšð³.pe’P…(¦(²ŠØ•ƒáFrD]µ¤~º¡êG>„ÔÖëKZRÊ®,óT½ªìPÅFW'J }hgÂ騠®ŒÞ:¸~AZj a.©€¢iX¤Y^ñåóMcƒ>òy.í’=‚É „ÄZ˜S¥;h¨S-yþ ¾)¼.íƒ>(g ó|«kkÈB *ÞÈ% ‰QTfµ'Ç.ppÜûµ(%ê7¨¬ ‰…üiò}˜íZa<ÔÙ6Œfö5õÑv±˜¢94W!.|CGAò¼@HùH‘öùÏfäÛqå ñ¸¡ÄOór$&vÊk”‡˜­„e°&¿ ¬jC]ê:cs­ÞìÌ1¥cÆ+3Û™¶ÈÞÍ’¶ýDë(Vº²#^)ˆe:ìï;·i/ZJQS·£Rü¬¨´ ñc+YX—ä«nDõúÅÈ~5•Ò¨3¤V¨.²À\)ÕŠˆx±,Ö“|ÆaÖ÷wŸ:»¸Z¦ËšR(œ*qñH+ªDEI)ôPgêuïõØr E@]¬Äά" À¶#´ê‘¦^ÏØ1Љ°¶/Jv ÓäØe‘x¶"†CdJ7Y”Þ 43Ðà-Â?Ó,@‹(I³ÒþYÝj ŽFŽÐãoDK¥ØbÑÇX#Ün_pôñpŸp5í€ÔÙ`× ¹™&ͳ1Ç}€úÆ LMP}<¾43—'=©º¦oÙ•ÊÀ*baÈè sc'¬&ÆýÞª˜ü«7Ø ‹%ŠÞ±¿ï+Œ=š—µ’¯4hd©ÚÄá4]®å‚æ¾ÀßCz†·x] G®lµFHlÞK’g`ö{SMo8;L^ûˆøN6C=ºï2CSwÞ˜ÇØ÷Õi"çnü5j{„ìsL˜ªû„Z®¬î²OQö±aê>!÷!™G›ì’(öß+¤.Þj¿²^›=Cµ”MÊ5jÙ„ž—ÐQt-Ö´vG)oOP€Fókc`zŽlƾD<`¬f+3ÖÔr'¶£8 óîÏY)¹ ¤þJÏKÜÕ¡gÖª{÷0™ÞдFñå” {†ËÖZ ü3ÿ©Í1r.lkÌx±PCÇÙ^¢ô{•q _2®«ðÚFT+¸¼IkG‡Þ”ŵ¡®£ÈZèOž›4r´WÉV4¶C— G’j ËY,‰|:_Íy>^»òè¦Â«ꬉCO½wYýIœ1Û¶A2T`’s¦Ðäà5üƒŒ8Á³í$çäŠhµD!vCŠþã·®°š»­ÁÓ§íq1Ó¢éA¯Þ˜DÛ`¬’gqŽ&ir/ñ4šGSŠ,¾”\qxã0¢–„z7E±<|ôh¹[^}Žn£G¤P=»äK€éÑÛù é]­ÓU†RH_ ÇîjÍÉcŸl“×\c@Ö£×¢ìêõ¹’1¬U’”†´©¸ ×ÝÎï$“=_6×ã$ÇêUʶ3_]êÞ3gCÅ”ô9>Ú”–Öô¸WgÛâV‹7&[1¹éKÚÒûAnâyÚ2Á!‰JJ1¨i…Ý?ÊZÅYþ Õˆc\y»·®ƒåù-K†Í`mþÉÊ…¢Ð6*—šÉÎí—íq¸ºŽñ‘Èà·¡\Æ “®=…ŠG±—Úu3S…y›ªm¬h¤¼³k„͆Õݯó(Ë ƒ »ùxÆ ÔïÀFy¥{ÇE±Ë—1ñ[XHKTGÀØŽV6ßW/îŽçÃ_3›Øº%õäê÷QÓÎLõTT^¸Hµ„ˆåÅ“‡tˆ=¤ËICò:,$ö„I$Ú†@ “j) 4žÀâÙ°[ˆêW1Šq-€ ÞkÏÿ-$[pjlÝ´'{„ztX¯C숙çûíþ‡GÑò0gu Ôpä¯]XmÇ¡À æ¼C[ÛŒYÖàÚhSÒf|X£?×^ÑÜìýLcÃÔäÞêÒÆœ·Ú"öÓ4’i÷³ K·6Êc€»ò}mÜV ~Á¸‘ ä“u㑉B]åùËCÍvèîÌNYÝ´ÂÖInàá»=ìŸEæêYÇn ©ì»ƒÇïÍ6[Yÿ¯ õ›ÃðÆÝ¨gõt¨ÃV.Ï «zÃä€l1ïs×ìÖZ¢ß3ã5jÀ ,Lû ))·bæV†|G[÷ë›h(´1øfãm­ò&ÃòTz{põùìo>Z|zÀ ¦Õç\¾–zÿh/° ë[B½<ÄÃUSݦøæ¯G;Uí ßöx{óØ¯ õ?ú9e÷8îó‚ý^„}›%R½ð±ÅÊZþYÖ®æ¾Ù¢©7ÒÙuDº!ái~hÊfÛÿ—sØxóÑüõ×lãt\ô¯èˆÙäK:zž’Ý£7aÁw^g+¶+[‘ÝÙdËäÇ´¸1.&39õ7¤SÃÃ8 M(ƒ ÀÙ¤ø¬•6‹æôÕyþ¢×z ë}0¬õB!î¤@š‰üÀÁ]ãgôåýÖ£ Žúäíþ|á—Áиìô4 œèSl»ß åQ€Ø$Þ1£‹”Ÿ)Y|"ŸJáú=%@ÏKv¹ž‡:6,l»ÝG¸‰7=c¾8¤ð‚é ÷Þóï®kþŸu¾ûæ¬í˜boùKÒé“Z;åMÙ†}fãC+¿Ç~Tå}‚žúF ¥?ÞÀT M‚OŸ¡ÚÏúOo9½”«Jk$×&7)½Ó8­:£yȸµ|]Õ™Ÿ¤ý­Æå»¤T•Z´àÎî;v3áj¯÷ú‹×,â®bÆ<ÛHÐÛÕ¼:Jß6á÷ÓOé ᤵèKr2|+r[ýR6-_»dCõ£ñk—¶‰¦¯]ÚÆj_»Ôßè‹—&¹þèΣ‚Y¾)!¯Ç¶.Ùx¥EÿÏÉx»þ›8ô\ü®s.¾Iúíâd—F¦çÖqxD½†3•žw°#‡]gœnØÝTÖÐhëVÝ­¯%'NÞû*€ûÙÖz¿òÅ­ŠÛ³¼Åüð÷|8|× Ð`q­R?¨i”YÐ´Í ©½`*o ”;2ÊÖ|¶1Óæ+þPKH…IIœ:È#bZinvoke/vendor/yaml2/reader.pyUT HWúWVMXux öµX{oÛ8ÿߟbÎA – 'ØÛíumw±èâPÄ9…–éXY2HªqîÓß II¤écï8±5΋?Îð >íS ‡b[f’"W,Í%°T‚%*-ðÇ® öÒüX*@g‡9|.JØy `ϾpPÅè ²¢x@þR »ˆHHp`øÉ 8 ®Ô.±åóÑ2ÿ‹Ã–ïÒe H2&%—@"†ð‰‡P¥HxrF(ž•‡|‚äw*ðW)0<)ÄX¾…TI(òì J‰K\]KÒüò4áÀ…@o\JvÏ%.¿3!¹@Wpñ¼PFNi§Yþ9ÇR ©´ep¶å¢¶mË#›ÌktKqq@{¥ŽÏÑi² ØÁ±ÞiK1Ø_¸@sq5U@‰ö™ØTjÐô/éÖ*ÙYV<’–Wûb+µ¦”H7¥âòJÛ%´äüÈùC˜ñü^í—˜á{UŠ\+ÊùIÁ!ÞA²g”g.¤+Î?2±õT Ê2*HJ!x®’Rñ=úæ®Â4ßò*Ñ”‡ z‡ÁpµÕ‚žœIº-9“{«…(:Z‰¡ «Å1˲8†%Ü&¼AöÛ5ÕDp;íDq°’Ž…Pðùõ‡÷šébì{ÊT"#4s4Ò• Žª°–š\Fè Õ9Äqš§*ŽCɳ]9; gU £ÆØ¨®RÏd‘£°ÉÎI¡>¡ÖÔú»ÏR'mY¯í3Ôź¬íðŒMH6_\1OÖ?Çät‡Û1E±<á¡o§N­Ã :óºTÇÁ¹ Lœ!a3ø ÁæIq8;/.OWp.×ù`íi¨$¤9¬ÇÈ4nB çÛ1QûeÎ!ôÂKËêIäF"êÕÓIXäÇRKñLòþ”9K~TlCÐ\ç–|ÿ©ñýÿá½› ¿ï©·CÂbóO”Mz…uÕ¯Y; .†Í¨Vìy‚‡–]ƒEú Âh0ÂP¾Áò{G„mNƒõ" …±·ó‘g ˜THû’„à‹÷Œ?‘G°FõwiÆgYúÀ-Sµ×ÇÖÞÅw[äµ^çïê¨LªýúÌÙ>B™:oíå¾o!£ŽITt ÝP_ZÊ÷jÜeÕ» >"YÇÕ>é2Ö±1Ó’öNýFvaÛ7pØ0/F]—©¸J@ø`úå6¤ÔwÏuMµ+½Çf`ü’vÕ«qOŒL9®.ü‡œ²|Þ™zéVÍì/]OË…X%žz's7nùOµŠÛ&T'-xGouCÒ·òˆËC-9½˜üÈr3ú“u§n½^À“7,ðjIÌ¡³L_qYCLcçWl¼êYб¸êt¿ÓäéÅí†÷qÕfôå’=ñ€;·Ý%*è›.á¢K5 Õ¡é㑎DÜïùúôâŸëòrqùBÿý5ê°1É<àµÌ™1d)üC³åA+.µi(;–ÕäĤGCa´—«ëÕ*XÀjè,aËnFïëJ@\ˆzàìô¯.¾àHI'Aï¶üP‰»}N@Ôx¹¶õ·TNH#/ÇßÐ5þ¯l '#ý׫(kEË”5ÍÑ5rR‘Ô;¥AË ¼„ËÁ#£ƒù˜ÒÙ$mCçf•½†ɰ}¤Ž)4cÜüÍoâ??­.~Žß_«×(X¹Ríbʸ¥ôK:=D€³‹Ÿgº5ý=v¾ù;7ßmç¦cg»ê¾ºî‹ïZóE«­³ðyakðãoãßÿx÷ñÓë7ï¯õŠ5|8bÉ…epóïõiñ+~^ãçíút¹˜­O¿\kp;½ÆåÛ_V«uy½XÐÕjõö6˜ÔµÝé˜ô‰ ov—L5ðìY3—œ‰dš« §ü´Há›9]“ç÷¢(¡ø:£z³‰§aûЙy15útí´ô –JîÝS80Ñ\Dм댺½àØÍFm“<ò$eY{Ú¢íjÇ­±ƒ 6¡îqÛƒµNOõ Þ̃À­/Ð72Ô§²bDª¾ƒÚ‚Rt£ÏÚÎÚ“š+È®ÃôtšÁê¡’‹ª¹§Ûe[o,ŸäjÞDµg“¿m,ÿ4eðV¯bïÉÔo¦[õÈ47£ë }ÕåzÛ+Õ=~‡#T=í㛳¾ÃhZ›Ñ«µøzÖ{^Í7nÁæúåÙDÑZÍ=ý²÷…߀Ùú^eÙÞm6·¨(l>ºÑÓ;m°Ù-Šý1|m6ôßÜÔ¦\Ýön£áØ,ë·Ãk{³~õl0ºm$ÓûÚ^¤ÿáË‹ÅåO ¹a¶·ØtA¯wJcO3äX5µÖeÊ´/S3³sÏ2:Óør¦í1—ÜGù”æþ:ßàšêŒÎªáR3ÛéÒ03)G£ÿPKH…II-ŠIàT êD"invoke/vendor/yaml2/representer.pyUT HWúWVMXux öí\moܸþî_AŸHëltvÚ´…ȵ @s-z9…k(ò.×ÖVÚ¹±·/ÿ½3CJ|¥åºIŠ¢ç—9|f8Î ‡ÒåyQUyÎ.ÙUòM!ø_ø¦å‚×’·Éœ%ß+¿Éy±r½iZÉNU[Ý,¹0mGúײ\–kÞ7ˆ˜³E³Ùå-¿3¹Ûpqt´¨ !˜Ï0ýë«·¤_³ iT±7­´¹ù‘/$å®XWÀ¡ï ŠüËt­·•,D±ä+–çe]ÊË¥=×"ÐÜØßœY}nx@¹’ªÅ–%ø/ˆaO(ƒ ¶g˜Í¥F5A"ËÇê¡aÞìˆø5—wÍ2õzg®VøÝ ­8VCÁDr¢wT[“›Dgƒ1Í hf¡CÝë½# ÈÌ?Õ”Ìk1žSv ¨]â:Ì~ý¤ÑÚmCžôßG¥±‰BÔγtÄÞrˆˆ¶–º¶9?ЬÜDµ›¿=Ѐf~~Q]ÜpaŽ,ïÚ­éÁ(YJ¾V¾[M`šs" Ÿ °ËõÍz3¦¥(uÚ‘ö sËLf¬¨—DÚwgJ7C¯îÌàužß!!Mw™SæèØ:† ½[†À±mt=:ýdÎo&"š±Ì¼c { Z(ë[Û®uÓãÌú­ü?eÕÀü®…”mÚÏ=A‹‰g[ºô¯ŒÈÒYˆ*M+­®nó tsõKé6“ØKê6¶•°{6Ô­Ïè0"ˆÛ‡$íè6„ÞÇîÂ1†ÚbFYªÝû)¶¾5Aƒ<û?ò^í XgЕ*uÎ+"¦^A®«ÄŲÐç)ÜW*õNg×î<µþ¾µl` mŸ¹ÏÙMÓT°×jI¨³Ù8lÀ;Ö Ì¤V¼ ¦S©m®ïS»¬ioçÏÏΞ_ÔÛªÒÕUûo›PG81“m˜9 ûÕnåFÙî†Çw ²6àê ±(ËÄõŠEh" UbÒ ¾‘ìøŽÿ¥Ò‰Ëw ȸ0[¹zö»dx|‹è¡,¨bÉ Ê‘`ã7¿>H„›².Ú]2Ñ­JòÏ$Îr(d*M8™pÀ::½}óD%v'ç3ÜG“{×Um€ì,Ë\þËP®ÐÁDª)¸ b¢óàʱ2öÁ¬)”«¦Ï¬>˲^õñþœÿêìŒZïïJ°<„N{‚;¾ôšNM§µ¤=âé¥yM|ê´O?V»‹Aò“ªÝvÉβ3 éÝóyv63‹¬.jÛ€:ÜK1{ùF€bàYÂ3bÔˆõòŽÕeX%n½ñÜ´äLÞ’NRÍš³Ý<|0C?= V¨ðÅêíúF Ø} 'þ­<ë³(×EÅ6E+EÆØkL:uˆ¼ðÆ1öòåKeçüü·¾ŒŒ%ØœxÍ?ÔÈÊm]H^Aâ$ï ÏÑ©Nš(—ZÖ^üB–MÍŠÅ¢i—äzx²Ñ‚¯ðÖ )›µ|8>& èxa*ï9[•ŠáÍ‹ C°$;K û 9a$¸Ý‰ßæ4áI/FÅôàŽ;Ù%• ÒŠ×º¤ÿ’©6ȓƫiÒp¯†iø]7b©’´h8¨“»Ün rá¾Gqè€Îàyÿ:áÝ”Ý!q«Ü'š±'ö„Ú»N0þñŸ¿r|8æžLârΞ@ÝÙ;$ôYÙb¯†«4Ä{vm/¥ž½S` ± ÅMÚV?‰_{'4ž`>©[ë*—@_$ޤW¡ ´Ãe¡¦õ>pZj;`g¥h@ô5DÌáp¼‹ÀW*`£­7{ECÊG‰—°äóKH•oU»±«OÊ=Q¼þdUlô±!Ïo¹¦’ç¹_µ¡æ.Ë· Ó©Ao”º ðKøÓ6¥ÊĈ3Z &ö ùrdë\U/ŠÝa~__Z_°'ÂÜQy‡÷Ì¿_¢aR¸q@þ÷ˆŠÇò4 ‹ëŽ÷€š4”JˆH‡5‡4 ‹ÃƒD ÒEÀQ–ƒ§j&òA6%fˆ©Ã'Äý‡t1¶Ì£Ð€,F6í¨3ü%$Њ‹?b±‘6ŸêtQP{³YàU¿Ôg©ƒXÛìàXýK9,ÊP‚6ÑOú0SК×"ŒYÅ·õ˜UÄÙXœ¦,DóÆŠ“Sj¯¥yO 3aî½WC(Ç!Ú_b[4kˆ“5¨¬\£`Tw ûäI›è $)ªa¹ˆš÷aü؃ Ç gòQ„§$íyÎÍÈ©lÕˆƒ88«×K¨>u–£ð}PÝsêd®ùè׺;í`ÒÈž£›<_7Ëmy—å#ižÿçZBœ‹ä)þƒ…¡ðýŽâþˆñÀ±NH¢˜€,Î Ç…ê«8á“SPXª£ ^.Ô¿Žm÷œmá„B-,éí­Ès,CY0ö1 K€ýË᪈²YäoVÖéÍåŠ9 R‡¶Þ Én°ö½h@Îv»|Énvh±Âk|8e¦§§5±ïxË> ƒ+TýJ¯ƒà‰‘±?í=Ì”(êå1õS/d1q×l«%«ùGÞ‚|ž8Àëtj0fIŽQ˜ìb5 Ý,ËBâ@ʶüXZëDG0\—ÛétµÃLë_Ï…:hþ¦âãúî–©Sv)µZ¡ªƒ².ÚªEÈ,(O5jœ¢{ʶEyÏÙ¦#Uð±`ÖÇÇÚº5ؤ×ôuÍïÕÅx„-®BÍt§Ä$C@™Ê3´m ®k(½ cYÚBº'S2E½fV'>ü›2@ãÞÔ³ïÝÈØ ´*{8#õ£B¿ä¢‘û7¿}•§î[ÿbŽ-A·ïéÍd¯b®+8t:Œ)4w埛ƿ W­«5ÉS³¬º|ä(kD²þ%j<¨ þ8AÑZ=aÉ•N•nµ¦C%Û;¬Úâ#¯“&E½ô+²wH Ôx Ó©^"åû.¶7>ø™W~C±®øh…¹Î&{*í[-¤.׬x}+ïØóg/Ü‹†tµ­ÉM©EèËŽ¸é5e%ôsf»WŒçV¨¤—ßî9Å$Á:д ’ 5*\¶š……e1£ëÝŽ!}grÇwýÇ3¸Ë'ÿЉ _@ £{P܈÷È©ãßû.ÌÀÁ=À"áç&y’±75Ý:áT9\5í8ƒ¥¤ž«³ëŒPÀm¨©¹Ò€Œh‚2 W Ö”‡qöò]6u"-4±ÝÐ祋kƒ2F†¦µ†û`ñÕ.<Ì'>ö>Ä>|Ï]™-K±)äâ.—˜ øþƒä¼£¦Ïå<tê8v­;þ0ôì=wC iú<7t:ZD—Øûúzà°ž3UGSõ8{ª¾T9}1»ºxa}a¿{é{ÄòÃ8±q=0½¬¯™ÐËú*(ºþ¹ç9þ£!Ñ\û÷Û.³ÿÇìYÑm£å¤,¤½~Àû˜K“ëX»=”œP×ù…Û··„a‡ g¤’Å·xh\±hCñpoÉ»ù†2AKu&èÓÑ6½Ê:ê2 ³Þ]‹Y·¿ bìDö¤ÆÓ$¾H"å(&”Kí›Ýc¦÷¨ æâKû’©Þ@Ð÷^ÚOr÷d`¶Ëì{ipÿôA€¾—ú§È0 Ù.£‹¾°œº¬<è¢rß%eü%àäàŒ.àíCÒd{À¼›º0”zh~®·/Fd¿ÇìýݧD|­ è“‚~³-+H¸? ö[òõ1**@‡_W¿ßÞ¹—(xûîÔÿ³££PKH…II7¦b| #invoke/vendor/yaml2/resolver.pyUT HWúWVMXux ö­ZiSãFþî_1äXI`T°¹©%”ßĤ¨b½›]¶*ãÈBƒÞ•%E¨(ùíén]3ÖHrºÀè˜~úéczz$FŽã†¡ã°S67þçfü ÏâðžcfÔÇ‹Ñh•ÆkÆÓ4NY°NâT°ýâZûþ»*óròúõÅì§‘µ›3¼î€ÑaàÂIKúÈýùW3 qÅÝöMºëósœ YÇÌx¸*ý‚<µ+!‡?¸ž ”Ÿ/:†%)_ò¸Zëûm¦¦fcŽ!f·ü!³UfB⬠ʂ¦,ˆ ØŽã@Ñi$ñƒwºÔs×öâäÑ´dÄŒ›ÅWÕ·À`¼Õ8g ëÝ!C°35;ã|ænBazwcð£e»IÂ#ß4%gY=­gÉ:Èë5w±ojÇXjt”4‘"ƒ×ÇìCù§hž›OÁ‚Ÿ°eK|‰~Š< Ö<nh3vuÇÙäõóâMè³®q£[îÛÞ2â÷„D.ê<˜¸sÉõkWxwÜgî­Dàr˜8œQ9€3 +!oDLc°PÔ 7<ˆnF”>O½0 TÌCŽœA}*ÉMB±Yš4XxÆZŸ?'ֲơóÒÒL¡ý¶|빡›Îàær gü÷ <^¿„hG:eDKt<áÓT8E"¯J¥8Z¢·lü^€ŒaÜM‡Ü$¼?ÜpÃñÔ2ôRyu³¾áiÍtúlyî†*,òrÄAjYi(É® +‹*ްW馂B“ô`ø£SX)˃ø¤â¬šÝW#Á"?¼8…$NâÈÏ0yÀ)jÙý]FBâȯ„v¹a•m*G~¤‹€™›êÊÌÊôv‡ïÈuTAVÐÖOuصp¶Ž;Û³šÈêr±œ_¨¨zq¡í.m–CÇÌ ƒ þд,U¤ yT·Øé){ÞE¼ô³h–Â-)jЇÐÀùÑB;VÕŽ¹¯Qq½¢Ô 2¾ÕÞ|rA–~QKí'ìóìöyuj†ñ#pª·Ý=è8ʼ*!ÌÍMMIÜâØÂÂd“*êæÿœT{Ñp¶A^Ï[Æ •±Œ´`ìZ\,Ž„ÔÌ9mo U-JÕ ‰xíÆ?½©T,_ÂÓ*•`«s…µ”'0‡Þ8õ„ì{èË9ößùurËyÕVઢUw[]k½ÒÒº«Kqº¡MlÊ›J®Î• .‰Ñvê–¢º„m$ÿmšêâ„1ªhèƒ3œX(_·v¬2sZÌ*NVÑ—.À`hUënX‘ÑtÂÊ}© †6ă°7M0înÆÌÛ@K '"ÏTg”í mˆ4Ì·\ÃÅ&mú.u{UîÝðÓÞPIêd^*¼ÏZyqÕêÞ¡©¹KsÓêcztKÎÚE˜DhŠ8[‚&q’wcíÔ§àïäð-Õh+åû¾°_?rÛ«õToˆY–V²{1¦»MçUNvå¼ѵuÃwDd0¹J‡`6¶¬ÆwØ-»»¹óaC%&]ê’ÒiS³½Ùíj¦ùGšÉÝ–$qbîĽX“ÕϦ¢2 Ì©ÝæQwGŒ¸sÒó]vÏpcÒêêe.6k¶w*iÖ¬ê[²Ý¶év¢zAj^•¢¥j‚]:­|Š3{–À ¹…HûgÜ_o]§G-(£_y[úwÑÝÕ™õÅ í25Þ$€±ÔÛè‹æv'['Á¦}íp((þ]ü±™Ô5ͪ‘q¶Í“¹ímq$Uœ[·z¾–3µœ¡4)«§(Õ8µÖTMTãÊÂkå`Ø>¶XNññ°¡cÙìÖ›"¦yÌxË… ô€q‡}á¿Â%ŠÀ½ ÞÀ쀃yÝÆÀ¥QzŠkcÿbXŒ³éQ‹Ù•sRL«v²†(Ùéu`Ôš¸Ûr¡vz«Å\.¸¥‚ª †ueh{ußC·þ0õ·6{uU¨°´ý"E¿«‘·"; I/YôxÒffNzÿ²ýÉ”_)懲¶þ5G­T÷: “1–ŠŠíÅë$€mÎ&5 ã7óìä‘gù{ü¾Í£8ŸÁÏ+möæjQ~E_oÞMó®.ùyñ=¹|;ÕKÅQþ ~fy¼Zå¯ð÷üÜúÌÀÒt~±v¸W…šñø>š‰«ÕyüÊ€6îIæ¯ÂØöÏgø÷èð»~9‹}ëÚ.ðŸ.p 8°ÎôfV;KbY+H"‹¯Ê«ÖAÍ¢àÚ± Zåø;;×/•9 ‹Ü(Ÿ¹³|6™Y½!8<8:~þÅ—_}ýÍ·ßÙOŽ,¡»Äàè¬=öX{C¾épGùüX fÞrW“Ãó~X±¨ùd_®yzË;½‰¾|ñØH,æã…±x¢ÚhÕö·Þ{(šÏèëÝå¥~ Ó;¨ÿ W7FDß3ú~²-"XCGꮓ¡¤¬¦¦ôu¨=ìH<“¤Î”ýæi\‰E>g×*Éàx‰´r 8E1éУ µ`vÿš×å«ÐVf|yjYðÓ—ó1ã©¡êÍøÓVŸR’|J¯…WqÆ÷ø&®~jËv…Ôéù±·¡×È"ˆ#–lÒ$Îðá…`žaÓ§ë†{î¶SIèâÓjI²j äS*Š7hÆþ‹É3ƒ^lû†ý$ëñ¬×ø½üY~½¯z  ÂÞ³}òþ?PKH…IIW‰·$–Íinvoke/vendor/yaml2/scanner.pyUT HWúWVMXux öí=kwÛF®ßó+禒ZY³¯ÖÇë8N×§ymì´goœkÓm±¡H•¤b{ÛÜß~̃˜rä$»çòƒä `0 sëNp0Ó4ʃyžMã¨Êì]”Av”Ó(8Ë’$»ˆÓ󠼚GÅæ-¨qøjoçÙúÁáΫÃêß½çáŸÇû¯öv÷Úë§á,ïÃd ðËÝ×Ïöžêjú…¨øèé‹Ý×öþñzïùîž.%^?ÛyùrÿùÖ[QñÉÓ?»õè­]Í,ʪ«‚@íÕ?UõÏ{øó§§¯÷à÷ÎÓýƒ¾îâÎóÝ¿¿xUý¸óCõÏÁîÎÓùqÌ“0N‡AQ^%ø¾¿ŠÂI0Îf³(-‹ N‰øŠ7ãl‚œÈƒY–GÁ$*Ã8)FPïÖñq˜$ÇÇÁVð¦'K÷†ús/ϳ¼÷öÖ­³<›þijy–—Á³0Mþ¹óì)%$ëe‘¯oÝ'aQZߪ8ؼÀ3‡rº8ÔO¢£«~vúK4.e A§ˆSPP‘à]tò–Ga‰ÝÝ¢r“è,8>ŽÓ¸<>îQr6x§‹Ùi”ƒ<úuçÑddšD—à ‰S é8K3 ê ”MâƒFЊÿkT°¡úÓ,@MÂWúm~B4à þ2?Ìà“øÃüˆèÂ'üeÑ»"ŸŸ.¬“kkkûð!“ø_‰N!`Œà‹.u'Ø/ƒ¸ Å úXNÃRËX˜NHáÏ‹8I‚iø> BÉ,…Æ‹q”NBäRN–ŸdÔä$ÎË«à"ƒþ€òO£ñ;Ô(·^<£ÆYú>ÊKR'ÓˆAŠÓù¢ &as‚×iŒ"?B|äȂp2)‚篟âGl Pax¸‹9Špa©°YTN³IÁÊK&Ì£è]?Þº7Ðð ÕN£K Úz9……y8.™ÈTõóè,¾ì'[oý¤ªìi(tæ]ýÈ^8ñ×&2β÷‚Ëó,Ná­9¢îß¡êE„ €E.¥Ï n³mS'Iî uÄA"bÐ@õE:N²@ö~ë½7½QprÄ=N¢÷Qlm÷N€Ê!(‘Ó$¿c°€õ%tfd¶Ì+÷xÛOã¢ÄVa^‚Y ›•Ú‰¤6=˜fep•A4‹Ë2šŒE½Í€UM ±d™_!µ¡3@xjaœ(ó J­·YC³ÆŸbš- sÓ *ˆÝNÏ“ˆ¦,Ñ÷îÿ‘)ƒ™»aj* A„¢×J<çY£|m›ÌŸ…W¼‡X“z]9Îã4ÅnI}%ævdÒ8[¤4sp).æ!hƒeøôÁ€ì—jg04[;ƒî¡¢ƒÃÕ¡éÙÝXmklâõªšÙçþ§Ø4SgIx.f¸">Oã³FX|dV…“,è(¤l&½5¦CœýŽ/Ž‘[Áa¾0ôüQ49 Ç4sœ"†ÁÎGÚtàKcD › ó«i&øÎgò‚O 'Ea€•Ò—f†%€³Ž§1 mœÇ¤¦£æÄè#L´¥ j ÅÄ»ýåÍÆ:ÝUÑü"K€ ÿ¡4ø‡hè•]7¤†ä+#a“3…ÅEÆ"TÀ¿}¸%Íæ—‹Ó$+ã…YÉdb‰IEÊ_§Y bÀ)±‹ÅP¬´A!Ô9 ÚyüÞÑ"¯Bðb#kÍ4Š&ÇÈ;Ñ`Ñgm辈IÓ(¥ l¢3+Ã7â{ó>yT.òTÈ-ò!*á’©¶:€AÕÓqÔgX¼¹÷v(« Üju-ËwÌ\Bn Åǘa°à•¨a2`œ¢Í›É‰0‰@½¦b%%]D‹èÓóBöΤRÕMmÇtìå§ï€kN}³l´ô†á¼o Ç\¿ÇéÖtêfhS{ +ĆGö·qÃ?Ó ƒ5ÔiÄ- ÃŽ@˃À^«"{œ-rTÒ–^‚ÑÚȧ<|@~ûÊö¸(p8RßGMg—û’ÐÜ*D‹{ æŒ0äSúPÐZHôJ¨V*E_az‚fŽ©Bº cÿUDK®ì´Èh˜z§È%Èǵs6›‡ydÌäÜÂ}Â9Š–ÃÈgX Cg‘œO0,<‰ÆÐ㢢oÕ°Ha<9ÅúÌkaàúÒXÔj;±‚tÞb+j£ò>˜%_ŽÚKÑøŒêo‹Þѽ^½R2VfË× Z)9šNï#owÅ Vt”¦Q]ÞÖEnÓ¬¨¿ƒ“l¼@!6ƒuYI-6[±°Ê· „ò"2ªG„hÛ ‡‡/¿ØT®íB*¦•1Êœÿ‹'{OžôL{ÎÓøi6ë“ËäÁú:Lñ†ÐÆ! c­:q“ÈF¢\´Ž!ê8™·Ï_*[|ZËÙŠªbIe…´ õÌKæ7­MF³‚ÙÌTj~Îçˆ{—ÖëÖºÙ¡qÝwËÍm¿]²ç5#Úéw{Ë–ëu[à ä°onrØ­IUטXûuiÍU"TWoCÅ(ìÇ…78l;8дßÖ6ò·)x­n:­R­öve1ÏŒ:)‰ÃÂÛÜ×­l¥ºupSXbøûñU;`ªìŸãÊðÜ õv+T¨é™€5•£ñ8“Ðóï‚öÚl­Vûm­Jàþ–AOÀ(mhøáµ°ýíJد rD64Ôk7JVA’-N»µ³ÖÚŽ€eµÆá¢(å*7e#8¨˜Á«Lv1l¨`û°‘ÅÌIuÚì¡Õ-¶GÁÓÿ—;É€EŠ s±í8‹Š"€\7±ŽUEÅC¢±×U‹…”³¾Æ)Û>wð*˜5&êpwæàÏÚ˜äUš˜O¢K<”#08×<Ó `\h%g5²Â÷Þ厫CÓs϶€ŒiôùX9ûcšŠ%pËE„ C¢Ú2Û;g‹t¬Ä L‡:ÒehÞÖ­±áAá¿õÌ D…Zhm ë ›Ö -žã±«%Úßâ8M;Óp–¬JäÃ×4ÿÅy¡·LRŽ ƒ‡C€¶§r±ç+ª‡ÉExU¥À¢ˆò²f{F öM‡5ˆi²÷¸+Lòþ’s€©z\Ú*»ÐFÈã\ÏIÍûý½FikrüÀß$rA¾xÔSNV³ŸçÒ•ÒVJjÈÅ¡e8×cKêÞŠÙµÖ Ô©›0)26©æGì¼b’=0Èa¹qûØpXÝbwÙ¨ì›êõ÷Mèî¥õ¶©Yi;]—K€}6;+½É÷‰\ºÚk6ö0(<€‡ U<‘4N0Òú­¯®—"2$‰©L›®ª Ä“ ôÚ\ÂèÂ&GÁ£E)Œœ ,9`¤LãSTqi¹ˆÑÍSPʸä ZQæ Aš XÀ7,x»Íà7þŠEÕx$ÚVðeolG±+c´l´|%¬Åéû0‰Ñ§Xb¤#utqõö¦'÷ñ×j@Ô‹™19mYxñy sÇD®ËÞœ0Ü nìâÚ0i‚Ö÷¦‰¬”cѤ|k~ e+fåB¬Õ S8ÜIí„8°MßZžá#Ã`-ºY‡·ñx5Ú¼õ–È„ó9úvagöÒ môI£ˆ¨A5jÃIÝ­è¢í˜‹¨"E*7ݼÃTÑ^"ÿÀK£Ceö®‰V\qÅζ¸…;ÁtïT¡^ÕΫùèÌ[?GÚ¸™LÌ`ÊÐ5›pØVÇd n=……Þ—5&GkmÙ¶È 7ó,ä¼1—ŠØÔãì°-"æÔCn"Pœ[DýÇì‡Z*Žš›Ø‘9¹sý’µ¾Ñ¸U»¾amT®~Wº“åV6fF.t ^º>‚uàbÃ0)J¢â=‹Ó¸˜:!Ä2,Ú—Pm[Cãߘ±õq(F-Žn}¦§‘îUÛ8t37­-âù¶•õ>Pÿ±|UÐzðl¨-\ N=誎˜!¨ø.Üø¤ãš6µ…‡MFXj˜V„ ¤2Œu}}½w£R„£Ö<÷…?øÅšG¦·hÁByZâÕ{ k§ª¦ÄVŒëW­50Wx};ôõBF¥+úËòOàíÑ Èžíøë´ôL@hkÈ©Û,Ùh KO+ÊD³ÖÑe™È,pɷĵWÖÇGs»†"ðý.JÞ+¬—FQöœ4Dyv~ŒTf¡nÖ›¶°`i.еÊÓ‰³X¾ )ÊÍMXõšÅXèTφDn>¯6jJÖÈãhI¡^·„ú¹yÐFÊò[±ýÐA”½jÙ9 ëHòGjçÏ#Ç"–Åcc7k†á54Â'‘Í*:€ü9ùCp©+Ìên<áF.Sqç­¤i5Iަ0‘1yÆ"Úùa,Í€d¸WqÏ©Éé)d ×¶Q…Ãmö}ãÓѵd?k,`N쪌 Q´áÖê4»0鶴|å@»Ýw¤~dw]t{â ýõl,·{i4²ç‹køÔ®ÏŒÈ ÂCã —ÙYà9·ÅtxÈ"‘æø<Ì <³•èUE÷fE“4¥Ùiq®½Ö¢HƒúKR$,¯ÁçÔ$Ò ·¤*ñìž|FU‚Üé#ˆ&!ˆ£2ŸC»¨˜X-—ŸB³Ôؼ²×ŸT±8ëœZ½Ò2€éü±{\³}T{äé³ ò÷>ëàþ1ZjT‹h\×4xŒÇ•HGç3:›Ì(fÄL.·åio›^ø|ÔVè’[‰u¢§зc¶ÖY±îÕš zçTÃ{ÿLìñéÝ…4•ÄšeÈ7;`T[@º(„Ö°]ÎR¦8õZ½±bÊ©§F©t¡ç¡:î,=qå…¹€CŸ²P6!”0ŽÝéÎtYÅ™á¾8-a¤ƒJ—¦Ƶklþ-7ŠJ}1Jay‹ÛÏB8¦ÑlIp%vŠO‘%å!‘„ùeA Ó«‹ðj4è$2ãÄnTî¨9䙂3òÉ  qP½É¡åC¯}bÇçš“;>z‚§~,=ÅãÓ6Í+= (µRM °*y†7θ̾ǜ"9ˆˆ¬˜•i“f3ŸŽ¦ˆ¦éG˜#¢—-&Éf›I¢1YÖ,Í_×4ÑÍ.cžPê´Ïi ü„⽌‰"ö¸& BÊM;ÏD ›÷Ûëç£F®áâ3öï|@êöîäi£ì´ð³ºE<¡˜PI’í ø©B }4Y|PÚèB¿jƒgª\ªîüð H­|,=\uÄ ÃcN÷Õá®z×¾pŠCI}Ê}¸Õû½çzrÅa­åàLHñ„fž’Âã!QA9›êÝÿ˜Õb4⇕0Ê ßê˜q*ÓK^‰FxГ>*ó•n÷:$jñf¯ð l‚l „1%è÷˜ŽÒž›V¦U@&ÞüÃ@ˆGx5'‘tì0¥& ˜˜i$8*ò£ôèò»?-îß»ÿýü¾çñÎvè¾?ÆÇ¼l*n«VÚy‚÷9:ß²ÍWm;HYíù™nE47ZÑ´1a» ƒ÷þÙ7²ú½í^ƒ'±Eð+ÀfÞ¿MØ×=× ³‚×9PiåtÖ’ÛeØî4.R?U—;NlVÊ><ƒ™fé:eŠbçÄ£Ëq47rQ’|y–Ç¡NùPÖÈ=ó@ï¾ý |?nãßñÇCüfþ\³kÝÅ×Ã'lëg4V9Ÿ(I¤îO=¾ì‹>Æ&LZá„ }dð7]E^ȵ"%'Fkç‹$RÙ˜ iú‚¤Ö$Œc^77€éZœwÀaZ:¸ú4é=yÒI„³iÄóP;I§,aƒïèi³õíÍá›·¿}¸óÕ×·x¬ºû·¯:…I´oÈo;ðžD=¨—û, ¯;ÙŨ™íÍÞ€¥"þDn=x²™Y¶7¹LÅq™BU¤Ã NÁ4xg&R3R™ž±ÜµU¯ƒŠ…E¤xž38®íÇšÈp9½*UÂ&òó€ ã™0§&ú€kuÒ ÒLfšÂ>Ël‹˜6Z%¦Â¤TqZÄ“ˆ%ÈvÏ(Ôœ¯ v@±ÐÑ&ÂQ{½3‚˜K?'9è0™ãAY þá'ògý\y[Å™ ð4tBGÐq"£jâ Ôà<Ë&btÆf^ÜÃð´Pû`9Œ£‰'\àúܺÃêÔ©Ôô¬8òéb04nV¨j-¢ÆÓmòÀï‡;º %¼ö|áœVGV@‰òlÝ.E˜8RYà¸ÄðÄÌKpbÄjŸ¨DÏx ; ébdc–ã”Z"‚ Iã‰³ÒÆ:I£‚4Ë&˜xÂÆðÓD—dZæÝ &‰š&—ëLÇ&m¢‰Ã[úX¥ ‰ìâ­Û±¦QÞ⎄ÛS»MØŽ¨úDåvLÊÍNš ‹vÚ€Ð-Ö9ŒxA×ZÂGqÆ4¼­ƒŽzÇ«6ÔAÎJk¡èéÛC4~áƒÇK^T-¥cüÄ\ìU±Ád%[A‚,ä?¦ü°Ø§*U-]…³„5' aO{DÚö½‚(1p8Üù¡…2<_-6ÿ;TÂgc£†Á>ÎÊcª4D¼ý•v×cUCl·° †wï¥N¼¥,I—Ÿnïšò.3±à ê•eAŠU}½{½àÁ~€ò=Yk‹ÞŽñö¿åÛÐxû¯Âñڒʪ[?¶¸"ñtr· +U“xm-¾º½×T_3¸äUmkúœ}˜Ì§áiTÆcÊ_VMj›L¤ºªìnîfß§c.UÜŽêþ‹†PÛk— "›aÕ·¡ÿ(ÚÊ,®Ë0¥_í®hhv4)jtÕ,ü%Ë›æ äUY–r{+è>!‡¡Þ9¬®ñHǨס ]‡³õvˆÛuäŒÓU‘ó KŒÒÁ'§´W}’Ô¡ °=ëÕyE#¬Ié!—úî,g熼qæX\ñ+¯åï™óÍIÝ™OUïÛçd5Å*ºÈœŒ:ÎF *×kf~—üM6±(àUײ qCË’ æi_ 7”°ìqèïÈŠÈî_KÈFzºÙžÑVWËå¶Ž78l]EºŠ!Ûy@(Nß(kyüÑ|ùrfÁOαºÅã¡Åº¸ np‰Ý]j>·é$wÐxâÞÿ.V>K.Bü¾8K°X0¢uл¤CGˆ&YTÈ$‹"‹¿`%L€-¬œ‰ ©»ÐpW.¡Ì6™NÛ:¤Úꦩ¡áä7]×o‚¯ œôˆoÙWyW]í9/#$>îi\df\IÔÔÁX3€˜¨W Coñ*u%…ôK²Èc…!*¶†vv÷÷ñD¥™·“'± E©•zVÄ—|_:{„\µµæºÏtAâfÏ”,w þ¿c¨Ik¤Áݧ áÆû’½ŸÔ3äÝïÝÞ¾ý€ûÇÿÉdîàœV1$U.‡%Ü¿,œZÁ[ý¾†Åê ›×¤€X|ÔËc ëûVʾřoÉB–%üQcSJT,çÞ…±LjUw=ç<ÁŠ¥"ÖX¤Ø€×¤tkßžWôDaêßE¡Ö‰ÎF*>‚˯ði¸ÛÅÛº³·¨²¢œ/u3 >-³ >ºY‡¤€w…—‹wÿ@­ƒ›G…»´|v¯7CL*yì§6u-Åå¼ÿ>TÇR›J†˜ú=¹–<ñÁÇ”.tˆºçØýf”[»ê« £ú°ì©Ú{¦E¯.-˜÷ÅTЧ€L<Œ¬ðŠ.C š+´¬]H}³nɵ^‰ö¾ñH<ácx+«á.™–kµÈ[¸·qÿüÓŸÿòwYÍHƒÛ÷ã©;z Ñ=‹§žNK7nuôÂàS­åŒ Cª3Bæt,ucý{¾Ü»·lZo¯L-‘Ûܸ7EØ›"êu\\øthgDVßëF¥_?2ñi2FhC§>ƒkdI1¨D@÷‘ï‡â†69õ5gÑÞ8{üÿ&úgßD¿¶¬}‘ûèµî‹Ùñ¹½*?‡±Úîà-t¥G.’ÚêÛ@ñSò¬ÆÂyVPÝNiø5lÃŒÀ1—K뇌~uÙË.-_™u9”Ö8ØU¿c«I6¤@(é X¡x,'’*êü Ë(°ñê ]ËKÍòçz®ÕÓ†ÞZRPÃùúŒ;×b¸}¢<ËÌ Ó¤¯ëþºÈPWêÄ1ÿ ÿ$uTÒ¼ó<œPöMS4_—ã$ óäŠ"N˜9d°(¾‰0[$¹5g#3D—‘mWò¹d©Ž ÃJ”ÒP*Ìé*FDR¡CÀÅy˜“¢S?‚¡,&"4ÈIAdìf¬Yb!Ò1µïfèr¦Ù·ôf†*D<­™¦k§u¾ ¯†ÌÊ‘VôƆ1Ô4Àa œšœþ6»´·jüëb÷ÙnÈÁ±w°»órïøÕÞ˧;»{Ïöž.Õ¥žµI¿î±ô %__Þû‹ñá´úðñ¡¬>|o|8¢/žiUcÇøð¾úðÈøpV}Ø5>äÕ‡ÇÆ‡HØ0AúÃ}³çGkÝ5óõ‘|}d¼~®Á|÷'ãñþ°cª>Фe|zÉ?)Z}08¹ûâñžÍÂKªvŸCZЫ?òW¯éÕwPßdÂe^Ì+É_¡U!†¾ë/¼ö¶pîèh™ìIuû? ¨}yUG:o áÓº=ÔædÀJµ/3Ž¿y¯gYý¥ ó…È Øˆë}ÛT\ú~D:GŠù]0ØbXr1ò¹ùìpÂÆ‡ÌÑr©Z ™ :C…<ºµË^M]Ý7ãé[·Éh3o‡MšÂZNyDâ:8àƒ#ž²Í[N‰xý˜±ð{ÇFpå±Üy´ûxïIx:EUã;SO·àz”uÀ]Â׫žÊ_Œ9ª#yØ£w1ã÷åtá,LTd¶äÓ©|$‡­lC“‹?”½¥ö4Ü0ØøsM=Si¤ñxš÷XM;­º3vpÉ,!Í;çÞ&ž\4·š‹å:Gì§¿5!?Ð¥4»H•ìU¹•j£Çš…Ãß)cÚ`-|BK¡ö ¨k ÔlÑûfzQF¢7—¾å ›é$5ËŠ‹“JK‰•²ÈžµÖ6¾ä¼^D‹Q¶ÛPt첪áÛµÁ]÷Ï[â Ú‚šcŸÜÑeÂcâWãj|†Ãïó˜éx5,ÞÕM¦Ó„1'¥š«òx)·‰¤r¢˜DæYbTÕ–‡¾*§ÍBSüæ_7˜õ&F2>îhvOMCºâÇ;ï Œi°ô,Wý2÷5ü°KOKFòQUaI'(¡L0@‰_±£‡²žÓ<=h¦‹ó\€¨ýá¿XTä0Ý”7 o÷l|0ë]̓&úw*”Ó“¦‘rq£#ÔH éøpÉ=Šþ݉·7Íz “7‘û½tùjFl–FŒþå™á0F|%e…IM¾\véV4 œ×SëÏLn7ß݈;F?*ܵq}Gפ{îi‘º†+[OŠÈÆÛOóMa”-IR©;BÔ[—@6ºß´ç#´`VƒAnnc’YŸ‹ŸåNçÈKRi˜ã¦0Gl¬SJê?ÉDêcvƒ J®Ôa.éÙaV«hØ~FrXGˆN »Ns!×’×2j¡×î¼WSùeB×| å0-Ëùæ·ßί0›Ï(ËÏ¿½ˆßÅßbbÃÝ,ÉÒýïSÞ•ºšÏk®*ǰ7:Ê•¶ö~^ÒZ¹Zö!+±²èôŽûžæJ©šwÕBPíW7ï¤T箲«ÄMªÅ·ë‘_“%ö^PŸv;÷dÐo¯Â&÷¥Jˆ uÁ¨³…8Ezšá´ˆù„a|™W|˜Sì/xC!´u‹»å}|á?ÂK>‘Ô¸9«`á¹½Úú³<¿©oÝ€wÂüþmÖ94dü‹ú7¦ëø#b‚do-Õà¡ÐTnr×yíêËÆáÓ…_¬'É7õ|<ÿÔãáã¿™[À3ݰR ;ç,Ô¸ðáúJü ÎxÙŒRÙSô2ô«ÈÒbè¡3áˆÅPïME«ÄÕ¡YŒ‚}øó}u?e\ª‹[»ªÀÛ¾ìL¹ë%Å [8n.™àF]¿=©Wêò²Ý\ |šÒȰ®\÷àW5WbÉ/Ã~|V$ºu^¯¦|×¶#Z2ßaF‚U« ŒËÛ¤+×…M&V1ÀÞׯöѼˆ’dêÎøuaͳ/"‡Ò_¿ÝÞüÛW[ßü×ðxtû¿>êõoÞÞ­;Û´èÙŸªþ®6 £†f M’€ ‹½¹¢ïHAçõsHùÂUV~ŠÙ¥ˆœéEK]'*íÍõæþ›Ë“h/­¡ÎY¼ÚáŽ÷ê8QùK†Þ{IƒlnÜ÷ß©±‚X™N©ðeÜ_ILFŸÓ`Ù âlå”÷í(Œû"£i<²P¬2·n²WSFg^R‚©]­ð¬LhqØkQþq„?‰)Cü´:;M]¼wl ïXï2—²˜³ŸsbZà|Wð·A@ë÷žøŸ®úƒÿì=õM´­†y6QUñ¿§Ð÷Yè{³"\$eU¨ú¼Œ/£î°9˜{WzÍa€Kž˜Ñš•ÓÌ 8¨_º¶ÃOm¹¢¬(whœˆíš])6/®Æ™xCŽNcR´·îÈa±O¥IÔeýyXÀBñÿPKH…II^5šK!invoke/vendor/yaml2/serializer.pyUT HWúWVMXux ö½VÁnÔ0½ïWDTÕ&UEÜ*å°‚EÚ‚Ø^PUE&™-o²ØNiAü;cÇIlÇÙ¶KÕ\’Ø3ãñ›7Ïžå9a,Ï£,ºœ¯€SÂèoàó4²þ–œ×|~5›­y½‰@ýEt³­¹Œ¾,ÎNõ´™»JŠnòe;XÕ%Xc³‚!"/|ÜGJNf>[4ÛÆõ×ïPH´ÑF‹ó·>~Î/–gŸNKÜE3§åáñ›rÞ”°ŽòœVTæy,€­Óª¢.iuפڪ{àvËh¶B.[ƒaªÒ Ý´®ÌŸ$×BšÌÕ£–:jäÝj˜Z÷0rÖU¦ÎÀÌÉ6Çß±±ÉíÌרDmçÕËòeÞ–1‹þüuMHU|«yhk'óv:§*Ïcw¾`µ5®ÀêUo¡Òµ²ð¤kÇ… ísâTOÀ†Êx%9ÍJa·TŒŒûšê’$ã}^ï ÐÏs³pWç„ ±úE WI·Ž/+¦€=â†,ï4T:^ž^aô´nUËaM53‰_™eU¶uÙ‰þo +Ú6.™.6{;pb ‡>oÓõŠ½Ï‹È3eÀð]]4DÏæ·€lBR\™ëäËW#eŽ*$I¨ÛµÄÓ ½,Ò¨•I­;¶Ó“bÇfPßü„žZ¡zÙ;â‰hå,ã»#‚™¼TWa¾ùùÛ¬¼FŽq"Á¤ë?Ö‘`$-´^zTÐ IR]¹Vð³Ay„sw»Ý³V§¿„Ú·r8º!¬ l%Håèö¼î•Qgd»Euޑ¸ÃX-½W"èž<ÌRGµ”Å/D!v½Ê¢×½ÙpCÿsrH›ÏÍ/M;(EDJ¸µÒAs":&9¤˜d³ßWSоP¡ÛÆÕ«$÷’ÒܳS þÈ- ìûœƒ¨*Uìnò~:„>Á¤$^'1Ôº³P<8¦¿Ò(Vy¦íå “KD&QÇJ5ˆxuÖò‡áôžŽtèÌI>Iý¹>‹q@ë<ÖYå3‰¡[Ú¯ë$nyÇ ÓVú3yHߣ3¡]ïžÌGÛŠïâ=³‹qµŽØit‚I­Yý+·`þCVLvîÇ}˜ÇŠ­'Ê·‡@¿¸ 85 ÊÔím%ÿï"[áYcãùL%ÞóÈòÊ©# —ª'˜çh@i]ƒgߘ—tâi2žÿPKH…IIAû– 0 invoke/vendor/yaml2/tokens.pyUT HWúWVMXux öÍTËjã0Ýû+%Ø7P&3´´³ÚY¤Ì&¡Ø7®[J%¹% óŸ­å¤ÐBµˆßsνçꤑÝó°ˆo!UñE€ôÈ`‹0¦Œ*Œ# Å6AR¡pIÄ.AÀ2»ªÑfмàE05d i–½œö¢ÎÙS'J º©HÍZíà€¶\ 3SæT1Îhª«m)Í [ĸ2X“Y¾Põ…6k¯GÌ%*Š»ˆÈ«˜2™Ã…óGNY´ gr1!š¡HK'(e4êféOqü¦”^Í]¾u‡ *Á–Žf2¶Úµ7»Që#%`œteÅApæöñê÷­ÛJû«»wfýg¦ðo›ƒÄEïßà p¬k*ô¾Ógr{Ô¬Ah’çp˜ªôLŠ N;)† ÅÍ4 X ±ó®Æ)OmË–†êw[£\†^Ÿêï7,;ÎÕ©;æR åtVi1mΣwpqÇtíuXÊ3ÊrúЋÚ¸à–CÏS½ª:uUðt·„§JËÁtÃ6ªËpØ×›e…nÉ~¯K:E§tÐQ™)Ž=ðð³à/'YX (§ûw4ÉD}ë±ø-þ| -è¹£>ØÅë.*á•<Ô9 MZèeA‰ôî 1QÿMzÇSõ‰/Ò%K¸ð{°á¯nâžä^Šä_½üeJ âßiÃGMì BÙ¸óõPÀè;<áË*ê?ãõ¶UY=ÁPK H…IIinvoke/vendor/yaml3/UT HWúW^XMXux öPKH…II8aÿX‡%invoke/vendor/yaml3/__init__.pyUT HWúWØWMXux öíZÝoÛ6÷_Atq Ï@±·ÖmP¬]‹¥/CQ´DÇ\(Ñ#©¤Þ_¿»#E‰úðG–¤V?Äywäýø»ã‘ÎlctÉ–Âm˜,wÚ8ö|6ó­N߈ʶÍAöVTnÐZéBØ¥y!L_¶¨Ë]·u–e·ÂX©«,c+vñÝòÅ‹‹™3ûïg >^)ßóRµ:Ø‘ewÒm3%רGºL-fâs.v޽&ÙWèÚ÷Sò¿peÅlÌJ=›bÃlΫ¹uFðrÁÞ+ÿué-={öŒ¾¯@qöÇË·o˜—g¼*ØÎè¢ÎÙ©duÍ<’ËD7`³ „ñ.©/úŽŸ»­T"ˆ/ó­Èo2²7¿leð³—Bܵp”QV\©ŽÕ WH»ÓV€y¾ã^Nqý=Jð-¡ëž.ç:Ù;êz:Çõ\—ôzºón+À¸±ÎƒPè¼.aX&‘Þ IwaA\#`ìª@€ŒØÁèqþ qO´Œpµ©º0à (‘apÞ Œ dÏaƒR)ö Gqx@Úxްæ|œ°õÉó~ï¶€^ÿ)r÷Ð\)¸ãçcð”DIÜ@ŠxÏPä|x,߈¬Ã“§%ÆïÂju+˜®Ôž­¹•¹ÁñëtFÄ•¼‚¹‡eì9ÓYð§Yá{9’ÐràŒ(¥›ûýi&¶úMWbÁ~¦ eå¿qa;ו̹ b²*@9¼ÜÉÂmýsT€áõ]Vƒd– §d%²5 vC =ô^Áœ¼cé c9ñó¿Þ4¯´Ì¾#5´Ê YY'x‘b”¾åª1h€ÚdßlËðо‚JiyEf_¿ ÑгæÙ´H¨ûVÞ¸0-¬ñ)bë¿téï"‰Ðâä-Áº}ɨ~ g$©<Í~êË–1) ȵ) Úà0HÀMGÌÂH®äß~륪úËq³HÁr9ñy§d.aÓpܸ~£¨Šžz(ïƒ †ì÷¯ß‘ç⯅xߌVM5ŽLùàGôH)–§eDÐåµB¢ìU“àš¶ ¸ÓñH£iUõ5ž›@S>%oiÆ!Ù°)Uùšœ$g§ÍÀÁˆž(#4Ɖ"ÁÁb‚&“@ð¢ÈdâÞø#¼™Ãq*×þÚ£·Ñ%7H=2ö€{YŒW¬ï܇œ!€§MDbDÆo%wùˆÉµ„³]33Ÿ¢—,0yÄ“[+¯+€V…¼ÍeÈ/x“ƒB É!·X¹VXmI«Íò-7-‹FMQZá:HÈôÓ^’-ß3ŒŠXpNœÝH#†.F@f_?ëë«ãÕcUÜæ÷6J¿ãº½úýÛõõíë• kçªG·'(=CJ¯‰Vmœ WÑI\ ÷k%•{È›ê»À˜ J#‡g¬Øv½, Ú¼Z¬¦L´2èÌm`9­‘š—‚ð–™ÅÔßÑŒ¢5‚Ål ¦BÕOÎÅ4ªXuÛ’ˆ~ƒÔÞ&µõÝ™ß÷Î. ¥x°r˜‘Ž » ªåa¯"Ë”ïë²È¨„6عxG¶…h$áY†[ep e16ü-2bÚîÑÜËvˆÏ1°<Ððò Ä<ÔñáCBFK*b:ŽMo£­ÍÈP]t(£ñ᯽B1z ‰¦±N§$ÚÐºÆ RžÊøÄÓ˜ö˜½8n§ø<™Õb£/ã[’>¸iÀï~Õ+œ5%ª-WdöÝ,„l"è€m÷5Öժꉖ{wƒ§aT!»*,mߣ†¾)÷åÚš+&Í}#_JXµvøýâ”IÍÖ¨[q\{}4ÈÔd›<ãoØ,[¦i—Ùã~¢ñnyަ_w÷Ã6ù¡üféDëmÉ?§¦o¾c÷ø›·Po/|ø“{òyWà .~~´GNú`¢åºÀ™G!·§¦i6;3ü g*ùaE~îÆµüUm謭êÕ0·Nîáqz öÔV®vŸ¬VÏïÿ+ÖÒ´øF”yö/aÛ¢Ç#]ª„*Eó虚9ÚC§3è æMB¯­ÂØÁÈ@3Éþ+eõ8MÆÚ2ŒÜ^ƒc–…q £§LA×}~ ´GA‡ù®µP ìB~eWÌ[¯ÄóñvÄ¿º þPKH…IIvë<ÐÒc"invoke/vendor/yaml3/constructor.pyUT HWúWØWMXux öíkÛÆñûý Æ®Aé¬cÎna4‚Ïn$m€Æ0h«¨ O\ݱ¦H™¤|V“ü÷ÎÌ>¸/RÔñÎpÐöIZÎÎkggfg—<‰ã$Ïã8¸á_’š}YuSíVMY…³ ü.YÛMÆÏ“.½é«ª‚öåÉɺ*7AÄðgm¶eÕ§¢±(SV·'âÛªÌs¶j2@6 Ò¤aM¶a³à¸zöøÌФ^eÙ,¨ µÞP³ß²úäd•'uØLL¾Mª·,ý矿ý;ýžÎ‰Ù-ÀÊ.–Àó‚Ø'›<^µÍ5(ç§_Ú[›]Þd‚HÙ:ˆã¬Èš8žÔ,_ ²xáÏHõci\^þÖð+¨Š­vU½g=0u:НXÁªDp±Xš )cÛ–Sø:ÉkÖrººf«·1è:±y}|³škV± ÿ›þ¤åj·a0“¼O²<¹ÌÙKÕ¡bÍ®*„Œ„‡y2m‰]±¦ƒ”ƒ )R‰ ˆûÐ(²‘ê‘­]:->‡‰=–˜ˆƒùá}-&문ÊY¯_õtÑ\' ±¨Y²ã-š$+@5ï®Ø&™Ú1È49: ŠâFŠÓ¤%À¬†Ï&xUì(a±óÔ'Ä¢Ù_E3"¬‰*‘ ·}¸‰Ztn®3P‚×PMî=fìíev:<ðZƒóQ÷ƒ¬pˆ™¬È.én³Ù#¸táð"_b°tw³»cêZÈâF1$í ‚G„„DPe[¶ù´ü°µxØ–:z$k¢(ó4ÆV9Ц¤î0;šxSíX§Žn-þ“¬fn¬Àé0£I1óŽôƒu¹ƒi¼+èýEŒxxÀUvY5ñÂÏôÀ¸suH4%¬Zì[Mr×»õ:û`ßzˆBé‰d¦.L:þ ‰´T&4w¦r¶­rfw£¥;—4Þ¹òꛬ¹ž´(§þùghCbXä¬0º.½}»„wù]´Èü¨.!¼5î¸*b⯟NYÇJ†ì¸2±ÜËéaTÍ  Œ±bî³¾[%yR½2ƒÐŠqLÉ]kª&­t{Ÿ»Ð‹À~šYø—+X¤B øgêq…3®ÕïÒ§`áÚn¯ßn'{êÜÝ©v%Nc‰€*{Ëö1'DêŠeÜjДà;×n-®ŽqrÕ ]fzÍ&ú[R_cVì‰]ª}À‰ŠS)ÑͦýŠ£1éùµà…R,¯bñ"5uj¥Õk§^³  „Ž™;%6Ÿ2{Ìy›dUýdÌ$¯¹Nÿ覱ªÐÓù-¬#<Ãmæ› ¢Ü)..‚>稨¬®fOÏϟΩsèÏ–ýuJ!Yˈ3‹êÝ–U“©Û‡C+m­ó¤iXáÉ4lXuÅL”)ÃUõ¹U¼äíÏ,(´š™:®ÈU兦ɡ1W¼ÃtI¬zt‰ ?~×*t·vxICd«Ó7BúEìF°C·Ø ­\ëX‚×»KÏPê:@IÛ¶9ò£š³¢­@3Duòº]ÊÓ‹R^ƒÓ"ýòEvÔ*¾[~ÊGR½˜¨ï]©–~yíK èé%†\†^I±ÃÀŒ.{ϪZßVÑ/TO °â%út¯ké>óöUðºïd/ßhƒŒyV7A¹–Mõm-à‘>›pðµŸ]ãOó}D1»úz‚ÞB× ’|<R¡ui5´v‡‘žüÐã¥cV®G„sïTFup#Ûõ°OIO±ËsXõõ[—e™Ç¤0ÚS€ážÕáœÇÍžÖÖâ”7ø†˜v (3qÓê´FH~ËîT ŸÕ©\¯å-­Ó/ºAaüºéXpôéˆ@5õ,è#ÊËBoe„˜ÈºvnáAÂršÛæ bãéP«RfW@=6æ OIΗ4¹Ï¬i,zœù{€ë Û] ^OæK§+Q:·º 5¶y.¶Ãž_†'€ÕSÔ&'üt¾œO§Ñ}8Ý“g>|Rw™ˆfÁ-áI¿>{&IoŽ 7 ŒUì¯Fòø™Èè#ÎÅè×1¹^ôùè ¸Y‘T{¿y7ÕÞo/=öÁŠO^…tÌP tìÊm›àû"Ãû_åìARãÍ;:a’€oIq®{0²8ùÈ÷6Á?•16X |dñÕQ XéuRCêXMä)Ë0e(Þå¾ÌWÉç£ÄÁ# Ø·ò'Ù> +Ubp/O~F÷©t·®s]Ñ®R žP…ÆÍ6®Ø,¹ÀÀ*æµÙÚ‰Éa†ÿž¼|ý|Ï’êÅâüì‹¥õÇ]Cž!ü¦,šk­Ã˸4Ù÷CM^ÎáßâM³üyüÐ,û@^?¿.wU?¢9±•»†½ècŸàjÐk‘öÂW?D»®ÚKâЧÓéK,1ŠðÍ_üëgþ%F¿ñbqöx9 ‡å\sìJ3}‰ÿ^þ† OGÿèvDÊ Æ‡ÚMÒ¬®%¬m]ÝøjjE ÑUUî¶XׂÚ]€Yˆp¬õ"ĦP25 †Út °3 ZtQ>“wqÂ¥××ËCÞ~™ 73ΞÿÞ·…E“cÕX§´y§FŒ[£Æu0iˆFú*sè!ïÛriý\ÐÅü™Çy6‰Õe 4 žÏ<Ü٠§´ÿdC—S²O°x¼=&“lp ARHßô8¶[ã9$Â?N¡ÙÖ=z¢õìhŽ’ßÖÁ¯Ý¿Œþ7·âôê91ÕåWjvè4cqCþÁ8ÝÁ©‹ {F°dÂuȲ|÷3Ö{¼Å:ªâû®WXÇšÀ‘y±mS—ç‘ o7™¥`@;zúUíyŠm•×.wüÜWÁnâ•õš…VóâO„1Ú?Û%/jî—Llœâ¼AAuäúì{˜b9/ ~äMjUñhü[gK;,øø*ÚƒU¹c8JYÃ*Xá>…þ  =)îÛ?ªT ÄcŽ:·Ìf–‹ì#“ …ç@nW‡-•gs{$AܱN¡G„Åöpz<’Õå‡$ð±*¥*ÿJ%ø‘DUåm8]Õe$iÌ…‡SEè‘)6§Hà#I‚KN€Ç’kª#È5ÕhéÞ#Ý»‘䎲—aæbºún”*œL=/á˜Xýüçš·ûæº,î:©Xw|Çî0ó-¯ß¶­ë·­Ç¶õè5˼ì:öî·vufì½ã–fÎ>xq Ô:H‡™÷Qhvл—÷bÈüR*qoÊt×"Oð­B¤Içé¼wûuðšÈœÜNäPÁ¨(‹3¶Ù6{"ð52· ‘4 D=“)Žù+”`‰H+þ†îm£…\%j‡BÀà ˜<ª§hÜ>ÀÍÔ–PZ뾎x¿zÀKÿðâ­6¸|Mzƒ‹'‡":9äòËus!9#ôŸ7¨'†¢p<é[gjˆð(õå.ËAÊÚÜYt ²@ ÷G1Ä!rÀ ½úìQô¹0,Rëµ´mMÆ6c ÖˆM°ÛŠüˆÞ}„§„÷Ÿï]hœÎ8ÖVàgwÍÓ+ÖtJÛãòµyk¼(âvG'ä&ðxõ"_ÇWþQ½|®‹£¹ZÉ—ª¢¹3GÒC´ëÞ4 LŒ¨÷I)XÅŽOEÅRU•ŒÇpe§vóÅѰ)HR]Õ<|{“ʯ»yÒõ¨7ö™;X¬ç9(â´žI„³h»ÊÕ{Õ†›–N‡˜¥WÙi»Nü^÷U-2#4˲³àe€Sd°/>µ&V55k숩ð)†C6ÎxQÕÔ°tÏ-PoX‚[ÍÍËFVtµ±<è»w4ã î””LC„ϱ<õ¼R†wÑIÐg×"Ë”—‹}k,MRoMY‡¥SÎÔîaOòÕÓ_<ˆ<Óž¼S½h‡Ì~©#!¡†‡˜™† Ï ®ðaðuYm’Fo ‚Ï>ã(>ç(æ""RRôSE‘¨Ùã·vˆ¥åœó» ÝaHw`n#ðý ÙC5‹·ç´bÕaø6†ù¶þâ¢Î™fϱƒ#Á|ïs˜]îpШ|NhÕý – è§ r°Á‚¾‹›â¯¥GÞU 5ÛMY¥µ5Ú¥†ç¦Aøè%Yµ$ªw=XO‡„~a6ÍÖkVÑ«‚.YsÃXÑ ½•GÙº¬®Ë:³C·±aU1PR:çÔ|óƒ^ƒéžAÑ÷1>DÐì+tîß¹QTŸJVs;šÎóèíØ½Üx1`µ“Y!(pòñIˆ-°\,½Ri€Ø€?ý2õ ªAR“T^WÍ6t¥h]T³Eå•ç (œt.FFã \Çz; [;»ý‰ƒØW`æ!Q5Û}ñ‘?šã…ÐIÈ7)Pj°3_JzjPY¼ôW½î¾;ÐÜõðíXqgS@-v>ïꎠ£mêÞÌA§Ý¦ò“i·JÆÑ»"Ch Бú£‚þj8ŽV»~p|,³áÆQÒ7Ül´ $Ê÷CÄ #u®ð°`|{tZS Š/¾FÑB×{X$¾k“qߎuÈûauxˆ`¢ 7š _ù "ÉAXu !ÊAïŽ(Ïo Í£Ý2‘òò´Ä9ùPKH…IIÿ’kCÃÞ invoke/vendor/yaml3/cyaml.pyUT HWúWØWMXux öíVÝNƒ0¾ç)z·iÈÀd7¢wƽ4¦©p˜¥%mqΧ·l-ý£&º˜pþ{¾ÃùÆ2Œ c£5º_çDÁ• ÈEŽÅ©CÕ‰²×!á¢kÚ !P­øeµ Â;Ò0D›VHŠ"È— Õ¤ Z•‚+-»R ébOÏ$PÂ讃GB+A×0Î2Á^"GɈR(À»úém…ïá`¸µ%Nβ=ú j„1åTc¼TÀê™ pÓ±WÓqCXr^‡¹.ÒÇ¿/§·Ep~JrÖGP>ƒ‘B8Vû?кçÃÒíµ[ a?¿¹Qžn6ŒtLc¥w ÖׂC>Øj&¶¡#J, œ–„Ù$Ê+ÓU¶´ÒOIæ§Á”ìLž¨ÜiŒrÀ¦³ç‰à&’ò…×–Ñ’öí©S#ðj¢„™‰¢‚Û`M6j/†¯ÓNvú}æÔAŠè/‹ÿðpØßs ãôx2‘È‹ãÉtb5™S¨Œ+¹™Ù§[ WwØÀtpñREÚø¬‰m›¾ÎÿÉÓ#ÆÌl™ÙrT¶$ø—3fËð¯(G3kfÖDÃú×ߘwPKH…IIŸó¦ßl£ invoke/vendor/yaml3/dumper.pyUT HWúWØWMXux öíSÍNÄ ¾÷)¸íÖ4û&{1zõà!ØN׉ ®úôÒZh‰çÊæ†/óe”2Î)%gòzx`»¦u(ÈáÂê@sÒ[–ÕJ6ä Š`ÓJeÈ3kPÈ8þ®= ZD"ÉÚ%ÿ íYÉ™Ödèø4ö+ÈeêP þ—¹®7ŒÕòû,#öTPJQ ¡ô¨×ÑFkŠÁí c7T›çg) ˜l5—·Ð%–LH%ã. Ee§qÊ +ó‘H² Û’Í“•ïÆQ}·“}&@ØHW ß-Çûq™2K#ˆ*QÂb¢Q lØU¢ÅÉG8Oi¸‚ŸNRÜ¡?îûããîulŒB¤EpÌb>Õ˜÷`9î•ÖðÅêÈPYWò º×áÚ_ó¨ÁŠ.g·.ÒÖ½ë¸6…mG"Ä=sO®™ßirõþˆ\;±vbm‹X‹Ý¹þ#ÖNªT&ÕÖõPKH…II¦ã‘sʧinvoke/vendor/yaml3/emitter.pyUT HWúWØWMXux öí=iwÛF’ßõ+ÚÖ* -JCiw6ײG±©¼¼g{¼–ó2»¢‘ˆ Hhж&Éþö­ê }T ®8ãƒtwuuÝ]}`c“ çiY&K–|¼L&eÁ’÷É~ågÉUº¸`å,açy–åð¿‹e<ŸÇËÁÆ&+ÊeÏÙ`pÀŽß½¾Ú9~wøö›æ“Õ@g» þËÒùe¾,Ùÿ¾zÉËU¡ ®,}´±1Éâ¢`&¤ŽnÓl0x.¡†ªx<‰³xy¸ˆ³«"-Dù49gãqºHËñ¸S$Ùy¼Z%óËòªÇ櫬L³t‘ôxõÄÈÕñ9þ¸ÌâtÑ“oβ|ò³|E4(€|Y2þÇ*/“©j3ÍWgÕK¢*G„"º+ðºŠ?ìBŽ<”ñßv‘ë¿í*îà ¦ûŠj`Œ]·0ÞQM,zèFÖ[ª™E1ÝÌzDÐFÍ‘¢Áo÷bxtøÃËwãw‡ßß¼}ÿ·á1´ûEDl€?+vEe|1¸ŠçÙn¾¼èí÷ûûQGUúm#(rÜôØ$^䋨yð:céb "/ÿùNË™ø›‘´í“u‘¥ã3ú3Ñ•ÃÂg“½«$ P1ËWÙ”Íâ÷ 7Vó¤œåÓ‚ýôa™–ÉO,^LÙe^éYvÅ~:ÏVÅì§]G ¤ Òìh¸”Ð^À¸ØYÂò÷ ß) ‰]YvÏ™¨v ±·@Jƒ›,†ãpŽ'3”æi9/Çù¹(-X™ÃðÓ ¬SR˜rÁ­ðjR®–Iá‰7“uJàX9šÜèŸáW¼,MTŸ¯–K´ÙÜlqJ"…Å`wW‰;jaÞüNEŸÈljìDHJ\¦ù‚eÐ Ójz\.“÷i¾*d]wÌò-Ñ¿(!8Á½9°¸™h°¾E”|Q&½Î—y^Ž'¢ šÅYá˜$å«êkIÕ <˜‰Ÿ“+¯ž‰é,^Ƶ´(ÓIDr‚±–r)œ²‰ª30²ÍPY²kƒð¦ ìà ”¬¸Œ'É3²ÂB² ™ª»2k2Ö1ÏAoÝAû ~€/Q÷™=~iûûöÛIž­æ ÿ}…$”½[®J8°wYjà÷ã Ì (+ÒM‡0K`d *‡Ã ÊÒ U—8°eü2YŒèjJ±ê(_Îã²D‹1MÊ8Í\ñÒvZë¿)!­¨vò»êØ‘±VŠ}]˜žK~qÝÛcOÔ¿OØ^¿’ñÑ·ûPáqßìI¼ÅŽÄ_O=˜öC}*ˆü7Ñcå= R4ZDfÇF!¸ù“hÄÃ9¨„?—ðû4Ô­¶úÇ2iñÚ©óô£g“Á»ŽUa‹Þ,“Ëx ò/&3{nþâ È¥¬5–µ$º4':Q$Àa$ï¨(¯2לë T¼£‚iZ€ÅHxPÐ5MÊÛ¤HJeÍÁ°/Ó³ú¨ø·I–FØj”,ÑNÝ5]š Z8œp÷ãŸÂY킹­ìˆ*º˜ t·y)ê. æ<_&cѪÓ%$D99úe~ÙéwýºåQà{ÊMö=ØÅ|ž2.’¢(b°8ç(#ì<ùaÁÇRMÜÎxŸûæd·"ˆ7‡I zÀ¢†=ÈeÆÂ6 Ä˜Oú§–=HF ü4î±ÒRc¸1tXctTQ^â»WÑ+ÉHÐÇÒÅ® z¿ô+á—׆üï&ä"!î@3ÊàDÕùÊê° LÔÁŒtaqcoàX3rtŸ)=ˆt² ÂCðŒÆ¨aWØl°=«(@HÝÕp1õ:RïÂÝì´ìæ˜Ç´^ÜÛÙsÉ#Jž°¾ßÈb˜ó²“%‹ŽAú.€àŒÛÁÕÌM@¬H¤‹“ Æpó€CUS¦,) ñÆ5\*ø•–Ëx×5Ï |Á†£Añ»õi‡Ì®SvˆïŠ´ o«Ú˜jˆ„• ·‰®¥A<æž`·ú—ÏßÄiY&˜ßVÏžŠ}J‚‚ ¯HÌkW>t¦8ÄY\ ë3L/TµˆHwâH@÷‰Åg¹öCîÆ™ô§K ¬ [åä/Ì×eœ‰©z( AÄâ’ªÇÀµ³ ÃVñÐ(>[ÆðºÀ€0!¾pyU‹ƒlcuMu³©PÄPÄñ°1,¾læ4àPÐaúC$`ƒà \­‹h£+…¦FƒÝ÷0XœèäKSº JcÅ£@{¦Q!szžÓ‰vww!vã¬o¯[£FA¿cY0–s]3ÌË2b µè(S˜ËóyŸtÌN-râwþŽJ³ÁDôòªv˜HqŒB9è| "ÝqZìÂ|BOPèüe†¼¿ãÃÇGào[„}"Z’¼ÑŸˆ?NŽhG6E$˘(îˆ_þ *lƒDqGü¢!üƯ äzf?ÛærzÀ„6kûnZg•°ÊüYºù`”rEÈ¡f0“Y2ùyÌóåÚ¼¸â¢°W“†ûúê×QV`gg'h”øk¢ÐÒÙÔyЙikŠi¸`(ï{÷@|¨Ð½_Sè;tŒïZR@º¿ëzp*þ¿™×,QAêZ-À˜õ¼‰RÌ{c_^Ì[[toù“ª[â*”Ë>#cÍBÎ;9-¬Ó¤ƒÍàhæ(Ó¤&(ã­D ÄÛÊù’Ê««ÿe]þk3AçÍé©•“Ä·ŒI(‡¯^Ùý4¾Z´¶áQ‰üêe …9ÌÒ¸ªŠ¤\Œ•:ᇠ°#Òƒkä¤_Î'0Ÿ“iÉNôUDÈ¿ªþÌWÜ¡®Ð Nšä4×t×ú€¦t‘ÄLê¶^Rás•”·j^U$H‘Ú‡cøm%Mn†‘¯èF7ðêÓ“sŠXÞ€©£^SFìþ‰'U¬5ítý‘·²¦íF³çí-TÚ÷«%å ™ü¡ªêóºIÏSÀ'𱻇Ž5wuþQäT°üt˜÷‡#u—r4nÆŒçÊl'maã™"Ÿ¥rh™up–kÆzãQMÁROjd^r"Þ±°J ÕP'+íº½ÂI! ó1 2$HüŠk¤×d³ºH¯ Ó(¢xIbšÜ§@n4„j–¨j¾–{³œÊJ‰¥is‰“/]®?ÿjˆ½|ŽP‹LV<%£':Mõ‡áp‹&-=ZZóâÎ¥j-´?”fVm×l°²Ê·2²¿|BFVM0Ö¯1±Fµöê'ã³ßѾþö´¯æ*xÕNŽ‹p³š¡Q±fýP•ÞÇY0é`ªŠšÛ ù5¦³~Ò¢fiÎcÔ³ú´G˱\góÉ ü§ãnn[É>9wóE#¯=–;ÐH“<íüíÀuÆ´¬I"®9‰òM¸NxÃä w@Mö-?2ÐbÂêd‚\Jé=¸Æ»v“œÖj”ÞVëMĉÈÉßúRýÙ:¢²ñ¯›·5[¬îS­ê–øC½Ô¬ó#õªuÆûšWÝÜM´uV;:öÖâAÊ… èz­†PŒæ)†æ£l^X¦×Þæù€W¯µàšmšÅÖíá¦B{ÛÁÙ]Ȭv7)l"~Z1E½ò­=¸Û 2»«(Óók‡=ê‘^'NsënLJç‡@ l‹H.UѨÓ~Ñ­ æÅÎYO–‚vw›±Kc0á ûë\7Å×µ…tõ?få{xZï ʶÍÖ~{[sÃÞ~‚jÄŠ-'µEÒLïvRµŒ·z+VõŠk;8`Qä‘аÞù²dqÁY§‘ÔÔks.ÓwíÕ2r{5¯íØ!—ã´RúÇ{Ì×j¥ÍCÃ6¿r„ÛLoIwàv[ ½ÍFj…Ö¡“äfK"¼o¢8Üd‘÷Ø]·'ŒUù:ÛÔXô±©úq§«ªÿÿ™˜+¡»†˜7DÁ“§ê»ž¢Ê6OØÞþcΤÁ3¶”X‚8ºAo¸J(´ Ò¨Š3þt™>Øï!aÄhúm]íÚ¹ôð´^÷/ÏÄÉ5äÜrzÎ2·ˆ†µ§¾Öò{@×=Ã(xçuÐʦ܎= tÙ"g¨ÿÚ¦IEj섃Ÿ~ïS? Ç‹‹h£Ùû*+9EÄà(„ì«B‚Ô$}a‰ ³md0ÎP/ · Þ§¨ Ö Pq~Ò¨F ªÚ›î(k±8Õ94fÎÐ6M 4z±¤¥R‚Q4zhTÝÔƒ $²lš“ô ÖUWä¦'Æàl[wzð„ÙèÂ’ßëwáÚ£‡ó<›@×Ãü5 ðI–qÖ´&»Ííb»»ZÐæ¦œ?q5q¦IþqÌž:¸iˆÑ<þ;†ÿótÁ#|YÁ"^cÒ½æ@hµ(V——ü¼$¿nOÁ°­éîÖ·QwÌ»ÞÄ8â#¯¢?2ãÔ¢œ<»è¥æÄûvQœ<¸9_%o{–ˆ+ïìhNÔÂøø}™>òy²³§^®ß!?s$2jüžrÆá°­%Î=š‰Á'³êŒéÉÞ:÷| wQ?bO°:üŒ¾áG‡æ»ÿïbóÝ?#„Aïú×G;cêü<5Þtf;5®¹‚‘!¼ˆ¦¢opªr1™i^{â#À’Â"¨ a‘ÇT=aïÛñNß­–ÉlµøÙ½&‡³ù€3ÙÉ{ ˆ(X„ ‰ÕÖ'q/¾}Â3OÞ˜ÐFÂ…ª§®|\[4åâ¿þôìÁà/_lÿ[o¼ûFQ§{rù¢‚ î+¡–ÒÒsI½'ØŠ>"H®Ö`äÈy«ŽŸX³ø?·÷¼:Ó¸Œñ²¯™¸ý!éD«ò|çqäC«ÔÛ´A1ÚÚÚêïÿ m^¾œ‚xwm'pëêð{÷ï9x"ÑŽ¶¨R;pŠé©†7³ éEƒõä“IÊD 4ÍT‘´Ž®#\‹cófUó.ãÀ¾yPÞ;²¼’J üRÕ<Š÷~qºh“•š±iäñœ:Ú¯ýƒ¡ R]‘È„åÔcöðWý} 6rbZ‚Ók"×®ð”]%÷oW|³äÎeWÁä$°ðÒîÔIŠ}þö¨f ¢H]$áš%£+2rSqb±Uð0QTê™PkÿÉöžlO€ÑÎ7‰Ö*ƒÌÆ{†‘JÂ×é«3·( œpNbß&”“TõÜ•x_±„H¨Ëµíûˆ‡>þóhµßßÌ~Y„—ië²+t¥9rÇΫ9ÂÑ—X@š0Mø‘ˆöÄ{ï)£jRAÄ{‚¼òé†U®Ä=%¡RJeÿÔ}ŽÀ\¡€^¸Öþ°¥ÛìœþòÛWüú“¦[ù)0Ò„™ªÚF7l4ž ˆ¹îš½â­¤ìÒ×GÃâ¯ÓÑ-Ž®7Ù1^´k4Ä oA„ ®íJÊÕÇûdYhôž¡hÜŒ/áþ( Þ ØØ;ãpÐ<ðUáñ{J·ÅÆu_zHÑ+Nö׈ZxPá´$Óh!‚æÑÇ}+¾}üzHÅĸP§š.ªñ¡Ýxõâë£#w„zx›Õ°ßwZ½ˆÄVx÷@¼$š]’pv T'uÕ/óbú°Ô‘‘ÙAÍm-Aލ¼HÊdRÊ/ó´Šëä¨$;A¥߃†ÔJÃ1œåÎ Ó C@mÇM³cH’LÔ×¢cU|ø:Ù:ʶU¯·Jà& 6eBjâíì“ΪFëÉuÁ;a>ú#Üè⬜_M¯µÏÑNs/ïÌ;J8jbR8ç]oï±§éPœóȈA·× Ê_&eT°)X!ð1fq)¾´ ‚ÌX WQ…øj•ÅDê#UD÷“TD÷óS¡n¨éŒ˜ùF=© †¹C;‹Å½QKA@{òرmÏWºê1ʶlæÞÆñ˜Áo€Ùôæ>?â·^ÄÒ\²œ§e5J9@˜ 50K %EY¯çcI@ñ¥Œ³ä"],°ßübÎEòAÄ1Hß|‘ÁÔs2I.ËY¡d@£3ìùµˆE Xh æü‹SdSjP?QßìS_³7TWœ ß^™Qb'ª¯¾cG ~Ж¤Gò;…;\âZý Bâc9õÜ]ÃkpÙ Pj¬¥>ªjàÆ;'72K´xÎwðjԪŗ­RÑø7í_u¼g5ñ?bmmëÿJf¶¶ø~T±gÀ¬{oã°yãk‹3NûæWâ3Ôxß~ÃeÖ.‰º/l|ã×ßCÌÀ#´‹¼ÇÄ øVjqg€ê̸Íaù×>‰9¹jK¬K7ïœÇá>|!Í;…Ô¯6\­tK,#7\‰y9ÌÔÈ#üb£Çƒ@’TÂámï¡#@¤ªÛÝ·pYG·„ã#>o‰Ôúz³óƒ ¹Ì§î¸ƒù„SÐøHQ愬¶'ÕwZG¸O³g!°iée<Äjͪ[ ¬“„Tiˆ–’#>×2%,T”>U -­xLæÃg™bÏ‘ÍÌ0x>Üf,ÖÅŸz© C<[xÖRÚü lYô˜Š eGe1y²Ç€×¸ŸE­ãõÜ©V«HÒ*?>²NßYHTÔÙ§¸Ü‚ëS¡íèM¹íßÖ*$¾…„7WÞ¸£Wôü>´oî„—©œû¹Ü½5á¹)nxüüðÍpüvøæåáóá«áëwÇÐí/L4êGõw?êû_Ë¢(v «‚3§àUP:‡ª`á|« Þ;ÏUÁ¹SðB,í‚= *± V„¿Í’‘Q2²=þ³öÚ.8TôŠÆVg/Š^z‚,ÑYð›úg¯>ô#ÃO0Ö#-øÃÑÈ•™¬ oúÛ‰ÉA‹¦Óô7í©‡‰X{o„ÑaÍ ê E§´†CZ˵wD×sB·öI<›Ì°QMžx',aãøÐ,l‡ZŸLf4ÍÕþ!Á/v?ƒ ÍQˆÚÁ^mõÿ£ì3öÃVÿq#°ÏAì¶=ßßJiš¿=a!´³çéKä)‡‚‡­jN[„Úî@õÞ¶[7Q F\Ž=ØFé½–}©Ó½ÖœýÝã¸s?ÙHïYñ²Žt5bÂJUæóP –êC'>Åhš”¸?g•|‰q–.ʈ‚ /É‹øÉ+ýJÝ?úˆœèŸgˆÄñE¿¯VoÆ{7oqøxf~­¼‚î#Ú±ÏM»6+jG¿ìeÿô:=l{wŸð27ø4îžÒ›Sƒæ“Š¥¹þ4Úæ•ÝW]ñ=Ðq ôe›ºÏÂZéòþ5‰ŽÚ}‘M‰S»ò'Kßuö ÛØ”›zÐËÜçËÂgYÕÃ7{ÝURÍåvÝŸÏ; ×2Jv_3_טſå¼ý—Lû}fÚošýׯZTº+WŸ–­Õ÷õóŽ¼Ã¦”¦ŽýœÈ¼ê®B‹_¯ÐâK˜ÀŸ/>Ô|k^wad¿ØÍÛ³›!“XݨW›áW¬Xæy©®l\Ëz©‹¼vþ}ÁÄ.¹Ð6Hƒ&Ÿ|_{Õ¬òšŒNC&çsßKsg»gnn«ºÍù8«~s^Ÿ/á¾W÷úk¾l¥¹·¤uX±†ê¬¡6_æ„÷·´ù ì.ÙøPKH…IIHVùÃÙå invoke/vendor/yaml3/error.pyUT HWúWØWMXux ö•UQo›0~çWÜ"UÀ ¬©V­‹Æ¤=ômÝûT:D‚Ó°GËÏßmÀ¤iyHlwçÏwçÏ^žuç£ÿPˆ?ÿ÷‡Ÿ÷B4‚&´ÈÊaéÉóÖuѶ@ÀÒó¿’m Ï+^É<ZVobàÅŽÅPñ’c¨+Ž“uSv<†Õa³a"†}SqÉD¸T1è#ׄ<‘ý¹€ †ˆúw!Úús½'Bzà‚š‚zà‚†¢f4õ™É¼åÕ~Ϥ9-Qâ2ýî8æ5ãÏr›~¹µŽVmœ=«~5œ 8}‚Ƀà è×·¬(‘ƒïìd!$.Ù,{ðß¶ª™1ù×PðÒÞ÷Q!óÅðF"kð³ëLd<;ÞÝf‡›ë›;õûÕw‰éxóÎrw&CbÞm;äàÓÍ|ᆲÏI’€?‚u˜(…Û´¬xéWeQÕnn/_Ï |$XY GiB³w¦ˆG Âõ¹ÓO—ÓÓëLzh§ùåä˜íÒa×IÇë M×ùàÔm ‘®PÔLj4§sÁ}€l´u¤Üí›"¢ŒSÐ0$Û?¾­­F6l)péÛW.´jÊÝûP¡²ÙU›Í´ÚÀUÙ Ÿ }A/8ñ #Ñ"¶µ#Z„Î%6¼ðSŒ/±æ„­0[f|ûÓl+«NI{q îk¶—UÃM&öÛzk qÐÂó*¼n°G™Í~–ï0’^r˜ïE³ªÙΛ™eLfjx*Ù&°’Y5š„U¨ÁFMO4WïIš«G“pÇžžmt‹'øž~îÁ³N\ñ~ô€!r„ ›yMïd1oŒ1QtC~b„à:‰9è0öV«Ft\ âBÍq›«‚ËÓúcð.Z€§Ùü£öCåí­Z-íI«Á›o†€<ƒ6Xò°Ã.2ŸY›·¾';q Ñàyf­n|¯ÏеXÕ5Kõu ™Ý ÎÌòwË>g‚éÿVíišý¥»ôÅãªÐ5†[?ƒŠnˆMæ×^¼Æ0º‹K¿‰%^‚fߘóQ¸ uÏê'Æõî‚âG½ýPKH…II`Õ¬áÝrinvoke/vendor/yaml3/loader.pyUT HWúWØWMXux öݑ˂0E÷ýŠÙ¦áLÜÈÖ…¥1ÍÚ„¤PÒV¿_EÞJâJ»bîœvî\c(%cp„«wBÃÏ S®= ^ŒbP¹¯!B«Íë²¼TÚÂÞ©&Á¢˜Ë%j3WUÕ‹za¬¾'VÍZš%Ã+$‘h ôÖý¨qF!n½P¸4Ó)„nmè°ŸÒ ‘{{w ª“rŒeEfó —‚Buc^àN;+XÆ^”s2Æúvëo­Û¹^ëOvy‡uN—aÿ÷dXÓ£ 4¿Ék؇ì¶çö™}“×PKH…IIµ ‚¬‡ invoke/vendor/yaml3/nodes.pyUT HWúWØWMXux öÍTMO„0½ó+&Ù ²{0›Ù‹g÷âј¦.æZ ¶E³ÿÞ¶|v7«&&Ú´Ì{o†ékƒ§JÁ¶.0®Ÿ_p§“›Ì(°B˜`šX!/SÐtŸÂ;å-¦ 4•šTT¾¦€¢p³ži‡%d¹eÍ?;pïyÈ«š¸_ÌAC:¦“Š%6²¯xRÏÓ0†¬¦˜0éÄãþÿ8SzÂ_؇r"<‡å`š)ºÅªÑ‡M4‡ ?QY]TYÓXÊ(¼H ÇS›ÂI>O9£0¯lëë )lŸ;èãÍzùtÕFeDÉ õ*ÉÉ^‹IÔ­…*6¾ÊCÙ2UbÿÔí{Fˆs5!f&h…„¤£#{Bóv”Séüoýž³ÂvQ¹Xôó0h‡7n¾­úÒ/•>ptó?>1S‘0ï¡3w5çæ>`µ8îÎ/ö¢äõù ñeˆ_Œ¦Á·Ímá3ïÓÜ@=.ˆ÷´i˜ØÅ«:˜¡}PKH…IIûI@¡—cinvoke/vendor/yaml3/parser.pyUT HWúWØWMXux öåksÓ¸ö{…¶&I× Ë~ìl·“-ËÐn[¸Ã0`¥õ­cgmÈ,ýï{ô²%ëa٠н×3ÐÄ–ŽÎû%9;{èê£yÇéç(¹AïFg§è& ‹0CQŽNOûO(LfäË2Ìr¹zñv¼_M`8U Ž5XÕ³IšM¢dàbœç“ÿ¹ÂÉØ0Î|N_Œ.­O¿¢e–.qVD8G}¶Ò4M ‚ñW$­Å‰Çˆ Ç8+t…fYŒ-hQhƒí4RY¸íÊ€ÇÂò8¢ÄiÝ«Ñs4:?ù׫‹c˜Â>‘›Ç%„´Ä q7Žñ´ˆÒ¤ZIºsy2:]je†c‚^Z´ÔGÁÕE¸\‚uWëT³ªu¤Yô{5©•OúãôÕÉ˃Ëñ¿ßŒÏOÆÜ~úì.XÍÅ;Ù˜ûH<¡vl0Õ àçŽR9½~ýâüù„âaw¿ÿrü®Ù’†Õ;:}3ö‚1Ø·B‘¹¡²_õìôÕjìµã¤€˜*Ùš Œtàa˜h']EÉ€<_YÒ*jÁ_ázùýXð°ºCù-«RùÀ¥ëÅ¡5”y LøHH®ä¹2{Åù=ƒ*ÄJ,ÙQ-\y[±p xŠs&0·4G龿ÄÉRí!Ô½z+„ïuÿÞ‹{ÍÑ[¿¯»âj¤ ¦ÃOº½maÉÁåÞ‡:Õ€Íø©øFÛÜûš)Ç5ÀcŽf fBüR‰-ß™LÂ8žLÐzß{MË„^€ø§q–¥Yïzggž¥ 4Ää+qTiV ³0»Ã3R•ÐQ|H‘Þá$cöÅÄO€´v7Ÿ†I‚³êöÎ4óIk÷k« wˆÿ^Â0u4»¿‡.#¨?gQA"•T °bhBǪ!R7…ć‡ÑÍm1O³Ïa6CE˜ßè3æðf)JÒÝrj&k4M JÌ-Îðp‡{:~6zsz5\ÿÚ©÷Sïý ¤{ôf¯o×á"¦ÙMðë/¿üzÈÝ3 3a òIÓÀar %$˜Áû^}šaA¼¿V@zgºO'À}ú…2yÂB$ü ³¢¢kåË4Çu²öÐ†èŠ (|¬°(²è㊠Ñ/R41¿dÖA†ç v>ðÄZ`GÙP¢2½Åà)):ÚŸÞ¦+#vB†QÄŠõ£tN?'øKèÜa96š›ÄºFÖ­@ÊC)fê#«”« ýAóšDwë’œN}á «,AWÙ +ÏÀFø$pÏöé>Ê£ñôuÄ>u Oµ­Îï= ã\’ÝcYtŠÀžs=z("âè*rnpáO íü@E<Åx†æ« žf?–ÂOa¼*­_%Ñ LqKœMÖ÷ÁUÃjóFƒØ²YÅ&m¯aU)°æ#…è%ÙÓÇ}"eXÉš†\Áw¢Cô†$ÁèK:ý’ÌS5£‡Ü„øô€ÁB˜dß5m‡›Î ²‰ìë@Á7Ã@®ô•*ÊPUC¨Ð„"G I7jVãœleg˜”J€ÄÅ|ˆƒdêJãéÓ(#Ùæ'|E¾è)ŸIyÊîi,c\'3ú¼æó Q˜Þ’3 e‚"lêÿêÒ¦`K‘ÂP!MøX¸2I¨‰BSYEìÊAp#9¢®ZÒ ?ÝPõ#BÊ ëõ%-)å ÷–yª^Uv¨b£«%Ð>43áÀtlPWFo\¿ ÿ,50—T>Ñ4,Ò,¯øòù6бAù<—vÉÁdBb-ÌÀ©Ò4Ô©–<ßÞ”öA”†3ay ¾Õ‚5‹5d!Poä2„Ĩ€F*³Ú“k8 È@8îýV ”õ;TTÄBþ´ù>Êv­0élF3ûšú芋»XLQ‚\š«7¾¡£ y^ ¤|¤Hûˆüg3òí8‡r†Øf(ñÓü€‰‰ò€åaf+a¬É/(«@ ÆP;—ºÎXãB«7;sLé˜1äÊÌv¦-²w³¤-F?Ñ:Š•®ìˆW âA™ûûÎmÚK…–RÔÔí¨T?+*-H|ØJÖ%ùªQ½~1²_M¥4ê ©ª‹,p'WJµ""^,‹õ$Ÿ†q˜õýݧÎ.鲦 §J\<ÒŠ*QQR =Ô™ºAÝ{=¶ÜBPë±3«H°íõH¯glè4X»%;Eirí²@<[»!"¥g+Jïš‹hðáÓ,@‹(I³ÒüYÙj ŽF?¡'߈–*±Å2,¢± Gxݾàè“á>áj†ÿ\©³Á®r3LŠgcŽuu€êÆ  LIP|<¾4ó–'©ºžoוÆÀ*baÈç oãXoƒ£ïÙßkàc‡æS­ä*íYH6q8M—k¹a 9+0ÅÓá^—ÂëX­íD÷’œÆ˜}m*—ég?Éë´?ÏÂf¨W÷AfhêùóûI :M仆ÚI û¦êi –+«'ìS”“@l˜zÈ}HæÑ&§$ŠýO©‹·:¤¬×ædP-A“2‹Zî g!t@K3­¹QÊÛ” ÑüZã˜v‹ÍØ—ˆŒÕ¬OeÀZXî4vGaÞ½~à*…"—Ô_éiˆ»ôÌQuï&Ó[šÅ(¾œRaÎpÛZ™æµ9FîÑ…mm/jè8›I”~¯¢MáKC¢ÑuÞØˆjE7iíÈãЛ’¶6ÔuY ýÉs“F®ö*ÙŠÆvè’«QáH d9k#‘?ç«9ÏŸÃWÞÜTgUC½“5qé©ö.«6‰3f4ˆC†‚KòqΚ\¼ä'xÖ-'—P<«å 1RòŸuÕÜk ž>m‹•ÔD?õê]G´ >*ig`’&ùO£y4¥ëÃâ»AÉ€×1#j8¨w[ËÃÇ—kqžåñçè.zLÊPP«K¾¸ݵÎoIcj®2”B¶)ÍpWëH¸mM.[rµuŒñWV‹²e×çGÆ 0ÎJPΦR‚\w2ÔcLÎqÙ“«)[Ê|u©3Ï\ •nP’çØX0´ -åªi+Wg;¶V‹.&K19åKÚ®ûANâYÙ2Áý‰J:1P•Âî ebY+ß#5¢W®Þîšë`y2Ë2_3X›s²€r¡(” Ç¥er8s;ecçî®c|$RølF¨E–ñ¤jÏ ¼QÌÇ¥uݬTaÞÆ–jg«)ïìa3aõ@ë<ÊòÂ`Ân>ž±jô;°Q^éÁqQÜeL¼ÃÒzÔ/¶£•MÕÃ÷ÕK‡{qçð×Ì&¶nEI=¹ú}Ô´3S=•W)Rá B`c-±‡Ædÿ ±ý·‘D0$o¸BO˜D‚mHAÄ0©–²@ãé+ž »…¨~£×Ôëµgÿ–â‘-6 -¶GnÚ…üƒëuýQ#ó|¿s=âò¨Xå¬hzüõ¯«ƒ6ts›`λ±µc6™% ®#4%mÆ}Û[{ér³7.ÍQ“w«wE3Þê°‡8)ÓH¦ÝÍ2,½ÜÚ(Ýwä»»l<áU ~ÃxD ä“õH‘‰B]å6óå¡f;ôŒvæ8§¬n:ç`ë7ðpƒó}öÏsµ¯±H*ûþàɵÙf+ëÿA²~w¾Ã¸õ¬ž u8¤å$cU˜mËÒñ†vÍn­ú3^£¼Ä´ߒŠr+fneÈw´u¿®‰†Bƒo6ÞÖ*o2,O¥·WŸ`ãÍW‹p‚iõƒ.H _˼H´Xõ-¡^âá*„©nÓG| óW‰£ªv†oÛÊÞ<ö+HýO…~NÙŽû¼^aßf‰T/|l±²––5‚«yh¶hjt¶D‘nHxZ§š²ÙðÀæÿ[8l¼ùjþ=×lãoâ¸&è¿‹c f“߯¡à8Ðó”œ ½ ~¦ø;[±3[ÙŠœ»&Ç#?¦Å­qù0™ÉÈ©?ZCe0<ŒÒôˆ2¨ œ-Aš!Ï:i³hN_Šç¯pí¡g°ÞÃZ(âN ô¡™ÈÜ NpF_ËO`ý8J°ðá¨OÞÛÏ×I~ GÆ@OÃÀ‰>ÅÆqÒÍPˆM2œ3ºHyKÉâùT ×oK”uî —ìrí†:+l»ÝG¸‰7=c¾¸¤Wë‚i ÷®ù+u7À5ÿÀO®:ß}sÖvL±wü¥Ãæt£ÖNyS¶aŸÙ¸gå·ëGUÞ'è©ïºPúã ¬AÅД!ðôɪ³«ÿô&ÓK¹ª´Frmr“Ò;Óª3 ‡Œ[Ë×UùIÚ?Ñj\¾KJU©E þà쾃`7®ö⮿xÍ"î*f̳±½]Í«£ôm~?ý”Þýí@Z‹¾$'÷"·Õ/ecÑòû•l¨~5þ~¥m¢é÷+mcµß¯Ôßè7,MrýÑG ²|SB^m]²ñJ‹þŸ“ñvý7qé¹ø}ç\|“ôÛÅÉ.9Œ¸L!Ï­/âòˆz f*=ï`G.»Î8ݰ»©¬¡Ñ&Ö1¬º[_KN2œ¼;öU³¬ ôaå‹[·g3x‹ùáîù:qø® ÁâZ¥~&"QÓ(³ i› :R{™T>(wd”“ùô0 b–ÍüPKH…II °sMmÆinvoke/vendor/yaml3/reader.pyUT HWúWØWMXux öµXoÛ6þßŸâæ Ë†ìíÚ .Т1P í†¡Ã‹¢Î+Ó2k‘%ƒ¤gŸ~w$-Q•6}7NlÝ>w<><ò >íR ûbSf’"W,Í%°µT‚%*-ðǶ vÒüP*@gû)|.JØy `ǾrPÅà ²¢¸CýR ºˆÈHp`øÉ 8®Ô±áÓÁ*ÿ—ÆoÓmï H2&%—@&Fð‰»P¥HxjFhž•û|„âw*ðg)0<)ÄX¾TI(òìJ‰ˆ%‹¡%i~ yšpàB`4{.%»å‚ߘ\`(8x^(c§tÐ,€‚‚€C)…ÔÙïœm¸¨°m˜b„ɼư{Ä+uÞxŽA‚b +R]i¤˜ì¯\ \MP">“›“„þ5ÝX'Û"ËŠ{ò²çjWl¤öÁ”éºT\^i\B[Nœß…ÏoÕn~1‚ ¾W¥Èµ£œ¬ŒpÉŽÑX6<œ¤9,‡¨4¬“ ç›!Iý6ç6rK ÷(rsyýt¦,jÎÀ¨²â™äþ ”9K~PlMì\Í.Åþsû¿½3Eÿ@¤5ë?y¢ì¤ŸèîôkÒ¦ObÌšC=ÌÉNÜIDAVyJv<Á +°æ%½aZñ)߀`ù-¯MØ©5XÎòlØ{34‚™i_’¬t¯À„5DˆÊ+ئŸdé·B¸OÕNo`+¢¿•ez[ ¼võÿú°ÉZUQ}æl¡ î§å-b‰É™ör—!äZúXä-Z2êˆâC‘憶f-6BNŸÃ'Q¶ìÖåv«õƒ Ío=ž»+«.[zñ‰Vì ÍîÖQoQ·v¯rß·R§D'¹Å‹n–‡/›EÿjØUÕåP¬ Äzu«¼1Õ¿ÃN~|>æn"Ô<ݯ1#ÆkW©"ƒø4ácÄÙ¬·>§á-WÔ@Uôû‚áKZV¯†žL™z\0ø5Êäñpªe¦û6³ÄtMÍgN~•xðnÎ ~q—ÀX»¸©“u$Ö‚wôV7'žÌ•d[jËñÅèG†«ƒØÝm8U'Ùè öF^ÍI9t†ñÕ˜jljœßÀxåÐA|j{Ÿy|ñ# ÝôÞï°Þì€M»dGeÜÎMwˆýçpÑ•¢êÈôþH{b°Ì—ÇçÿY–—³Ëçúï‹ ¯QÀ9$tHÁK˜M£'üDZyÐJJ…K3eV%öP&=š˜‚v^.®‹ Ç¿uÐÁ–Ü„ÞWU€¬ïõɳÓǺì‚gKZâÞ%ùádîvC:ùQpäbó7VNB£Æü~GïøOa  #ý×!*µ²eJšÔoR‰„m¢´i¤rªVP-ÁK¸ì_J1j:Ûç¤Q»òoX§I­õp~ö–ÔG…æ”7}óë‡øO‹‹gñûë¾nôÖ®TÛ2n%~K§µÐbrñl’ñ [éOÁùæGp®ŸŒsÝÁÙ.ÆoŽûüIc>ou{–Q/li~üõcüÛïï>~zýæýµ>¡biïX‰aðåËãì~^ãçíòx9›,¿\k¾;¾ÆåÛ_‹ey=›ÑÅbñö&Ußé¥ô¡/~êˆ÷LÕ„Ý3•œ‰dš›"§ú´I›óëS¼OoEQÂævìäë¥=ÛÛФAcãO—NËŸ`©ä[ ‡<êk : ;Ç`/e¶5¥VJx’²¬}#b°'°¡Ã+v>Ý ØÃÀ]úð1`Ï¡·7¸iøÕ>ÝH1r”oëF´.ýõ¬ /‘µƒµ '57”Ý€éé´‡§‡J.:™ñÀ;oûí0壇ü€úDQÙ¨£o[Í?L¼Õ£èš&IèêÖ} Ò42U{ã5íîÍý‰:=íÔ<ŠN|»Ñ¸‚áõê§?Ïx»ùΕXßÐ<:_4V}•E¿ì¥âw0·¾zéLFGÍ­-J[“äèñG{ÔìJÅf¾ullÕIåêÆ»šúb=lãôÛ?tç€GÏ“{׿3½ºíÍIúŸÿ<{ñÌ¡57Ëöª›înBÒ=™üº@iì92;íÄûîiƾ‰¶„G/=#879ƒ3MVgÚÈܧäCR˜7úëtûYhÖÀhpv:»je{x5ÊLÊÁàoPKH…II­±–Eæ Ø4"invoke/vendor/yaml3/representer.pyUT HWúWØWMXux öåZmoã¸þž_Á½ œõꜴÛ²èÐèŠÞE‘ZƦÝÉ’O¤7ë¾ü÷Î I‰¤(YÎíµ(ê‰MŸy!g83ÒYžó²ÌsvÃn“o¸»FHQ)Ñ$s–|Ï7á÷óŒÁÇú}ÓÔMrwv¶iê-ËþdÅvW7Š]šÁª^ Ù ž™ok®„*¶bÎäAÎÙªÞñ0gê°ðû¤ûͯÏÎV%—’…,Ó¿¾}ÿú6[’P; ²ÄbË3¢8ðm™7ݰ#üã_ÝÔv_ª"B@k±ay^T…ÊóTŠr3Ç!Kr©¥¸ù®®D7¶)ë'gÂȈ\›yK‹÷;NÚ!:ôÝ ¿¨Sb×÷?ˆ•r”m©ôLþ£;Ñà‰¸óçyYp Ó˜C-:K´øÖ\qGGÜpXã‹’#QJ”>)šXý]¤¸nö_ÒD‹U§Øh”⡪‘˜i@5ÌÌΊRŠ#ôÅ:°åÝì«Ù‹VƒFô—nZ·àÖǾë­?þ„ö…# üÓðBо;ÕˆÕ¾‘Å'Áì6óF¦°ê'±^²‹æ+vÁ|ûØO#Ô¾©Hoî|PŸN•pŸÚñŽUÆw;Q…ûƒ¿rŠX€‚ÿõ|–çÛ¦Îsw;ÒÛÅ]»7½¨äÛÍÝ•é­yçžÞ‘C·Ý.D9:”þž¹rû2÷ÃeuTƒþÂN¸îç¾üGo¦¯¡7õ—“ÑÇÅeTˆg°?™qÌ"ò÷+^òæ;ø‘ê{KªFÜ ýùHÔ!‡žæ`žcºÎJƒ¿£{{+Ôc½nC3_¯]ÅÓU)çÝaœ3gÎÖ(hÒ³[‚»àšë¼:÷-„3± !:ža²’v¶Š9§pœ™#:÷ÎÚs4ïØ©úGÓ ‘ÙA[ŒúyÌ"þ},錚s®8$†Ÿx¹§³I«ðò£ä(zûؼ©Ÿ|…9‹ã}žô÷YwóÔ[õ¨§6?íEµ®•ìØœ '¡¤•Ÿ'YýÍêž°îëÔ÷Bvó‡fßÍàV(±ÕqV+пFs"‰§¥8åÇQãMi!‹J*ŽFnAæÎ1™1^­‰´Î´múØÓà‡(í‘¥mšÑ¢y6vª€èA·Û©÷ÃÚ'óJŒ˜ 7o«Ó{êÁÞ‚ŠêÁ=×fèyÇú½^ü?uªù#—\ÁÕÜêžà‰Ip¶Ì4”…T–:#ÚtæŸkÕú[ÖH¨ÌÅÚBøKÅç•Ø)öâ7%í}*Âí땨ö\Ó^jð#Nªë !ÅéY‘ÝøÁeD0ÍÁIÚAÿ†Ùçº÷CsYê°ð%bŠ£`‡<û? 0A­öÍBmJÓP ÚbiÐ`š-OcaŠ*t„[§³;_O#Eœ³ƒ°2÷9»?(j™Õu žV) l\Ífà‘ [(£6 ÜËdh Ô%f|YÝ<̯‹ëeµ/KìÒÿY4sQÍ—a@‰­ƒ"|7‡Ám°V7AbHàÄ=Ã(K[vcú’™ChJþµÀ‘4árUÉXC=ð‡:Õ8Z{c›Å&ÿŒo žžÑóºìGÐ"Qpœ’m[ ºTò|e@¾ÄÜÜQŠ¡~䩜(ñjbB*ªM{Ñ\‰_-4úôX@BÔ´%˜±7ÁÐe7騵E¼¼é~ÄT#w&À’þí›êvÃÙ‚îûû*[Ì6'«xå½1‘ºoh= ¬5 àUˆ0tŽÈ°ÚO°1ؤþ½~7L=rEõ@½l…±˜}ìV~ìl+×Ñ’Uûí=Ô¢>ØSú^ž€=Y[^BšÓ(™1ö3—Á:ÆÞ¼y£e½W¿ ed,Áá$þsY“ÚW\‰niõ—ª¹W9 XY[ñ¹*êŠñÕªnÖ@br ªüE°MñY3¼?`¢–d‹®ZPF"&ÛûºÌÂ!É6@ožB¤·ý[Ýn¯ÎX@¿’Ã-kæÈÿ]=?’®£Áƒ2稃ïxAM´•>>ì ÓŽÕ»&h¢Ñò iæV¬~h=w¬Hì8èÒSíw¢›£8T!¢ï_G ðc…÷SCÄo©žÆØ#V·MˆØí,~JÂ.åy¤L;+"òÒÚ@[;î#òÀœyˆéYá6ñžÝ¹;i”÷$1d·Ñ£†í·S.*«Ð>ÃÙŒì8ÒpÌC£Ëþ6ö°äd‘A¦që@쇳BÖ ñnÅçG|ú ¾µÝ)O*a?#m›&5Yu—Áí“è8D-ãáN‰“újú$Ï„¦Jäy˜ùÒ°ÕÑ%LDzÜ`•n:‡Ýâñ㣚ˆ3ت‰f_Ñ}'ÖN6ðrÅ+Œ{-xó,rÉ.d÷èñ,¨³ðA=$ƒw¡&\ãW]@±Î›€dÀt½8NS±ôœtà°†€dÀèòŸ§‹æã€xÉOÁ£d`ÂyÁý â¡§MCº)'OLB²)²™`šá—IBÝ©¸ôVÑDl¤€Omš mì™EÞ\JƒunߨƒXÕ[8ŸG«½ êSáE{ ÉE“˜ˆÒñ²_™ÑðˆZ äÁ 1 xi0Ò–ã¼[8¡EÓN½bw(몯­Çr(òΓú #ù°aK˜cL+>”oà Ff²3Lžoëõ¾„;yn/\¤ÌóŸm"„Y&/ñ¤ ñÖ‘æýŒæK¯ –CóI<õEò£°¢ÃrwI@>¾Þ¯‡½ýã·X-KþI·ÄÅš²¥ÒÊH‰ë‚ ëj¨ÇÔ#»~õÚ¯VÒ;Zañ=g¼ymFƒáœžÑ€€Šé«ÑE/ÅþB#V5€Íð Ðv3ÊR2 š^"è ›Í %£’†…ƒå0£ŽeˆVÚ×­0Òe®o™Ü‰UÁÛ(ØŽxBN–»K ¬ÄØ’ÈŒ}[QCÁÁ"TYiCë¢> ™çvq— ¤—Z5_ñQ4q™ú;û£;«ìò]×U¢4¹ßÑ»¨…ò˜ÀñØ55"Áú/Ì Ô’ÎïAV(Çå!d³åA?¶è¸@†î½2æ†xœÃ·5ô+±Ùº;®V¹â÷¥króf€ø î¾{鿱œøÜÏÿ[¾; iz=w*èxM19wo÷È;ƒ-gJäRýsöR¿Xuùzv»|Ýu&Nð]êM"V»—ا²t”ÝMÖõÑÀû*ºtrÊsXÐò~ÔÖ‘®í€ÇÚñþ!$W+jX8!ÊA:‚w 9€_÷¦½hêjéÏ)LxFî‡Î—~WU‹>]‰ È™‡Xø]6«mÞ¿´»uwϘž©© Œ²O‡»Í¶#ݦý­w³íJsŽ©ÐëI‡) ?[÷Xàç˜ýà÷Ìb<ª§é˜rÏÑhð¤Ž&èJë»q¤†¯MM¬–àDrgNx?Àu´|]=¢ˆD@ô-.¢ ÛYZÜþŠF‰(@;Kí¯éв¶Ó5V†™|½KlâU˜!;ÔÝq(Ýn?„oeÁA'ÀÈì±Ê‡/ ú;(!üE°ßSüš©#]´ÿʫɵŽj*ü7PKH…IIõ îï]t"invoke/vendor/yaml3/resolver.pyUT HWúWØWMXux ö­Yýs›Fþ]Źl™±ÓoO\¿z[¹ãGIg¦©¬¢3œl&( ÚžÒþíÝ]@ÜÁj,ó±ûì³{{{{0rŽÃNÙÜø?OÅ‘FÁ"1ÆÌØ/F£U­™-’$J˜¿Ž£$cûåÅ0òDZ_•G‰Ü€§)«p¦¨m¾Ÿ¼¼¤#ëdÄàƒH%)38Ñí§ç“w—WÎÛ&—“7ÎÕä'àjdüö䑯;JnÇÏŽžŸ¤Yb¨ ÓŸßMg?L{TÄïªÊËÉë׳Ÿº5Ö<6 ZxÝOßõ3')9§ öç_µ@̳»æMºë‰s?]ÇLE°*ƒ<µ+%GÒ±hUEi‰Þ²Ž{2¹›( %¼?x°x ŠF©<€ºY߈dËlzly΃މG5,òrÄAjié(é® /‰mаWɦ‚B—ô`Ä£Sx)ëƒú¤â¬ºÝ·E ‚E~¸QIG¡—bò@ h¤˜e÷w>8 ‰“A~ÀðJXàªlS 8ò !]2˜‹‰ž¡Ì¬Lo^¬ xïC®£ ¨­*€¶¾ª•~×RØZ:î4K`5‘Õk\9¿Ð> ¨v±Ÿ¢ïœ6KÑ13?…4-KU)ÕVò;=eÏÛRÄK?{f©ÜÒýx½œ-´²ªuÌ}éTè %ÜOE£mùä"„,ñ½¢"–ÖOØçé'ìóêÔ ã+NàTo‡{0p”y[¨„07,Õ%±Á±……É0&UÔ!8Ìÿ8© ÷¢álƒ¼ž×¾Œ*ciÁصvp±8R=ä´…PZ½š²sˆÓnAðÓ›BŲ… Ðæ–)T[í±VØJùsøâ\Ë,«Ïa9§þ;ï¢.6ˆË«´W©m¿Ôµ¶+­­³º”¦ÚD¦<©ôÚé[)ê’VQm§j©ªKÐZóߦ¥nœpŒ*úÁN(Ô¯ë°v¬*sªøf5NVч.ÀahMë} ô.Œ_ݽâÎcÌÜ ¬åaæ„äbuFãÚÞ3ÐfEC¡á£È6IÝ0©[Ÿr_…ŸöfG2'óRá=Ó’‰ËM÷îIMb\Sëǹ[s~x¼Ð®ž¤B¹î4Mâ$oÆÚ9L£¸SÀ¦ÑW\ö}á¿^²Õ휭‰Y–V³{¥»õΫäêÊ…ymk1ê†ï‘Áä*‚ÙØîŒ>ßá°ìæÎ•št©KKgMÍöz›ª™æi&w{G±¹÷BpKV?›ŠÊ40§v›GÝ­,âÎÉÌw9<=E«—IØPnÙÞ©dR³.7Jc»ÑÒ/íöAu_j;¢‹j}5­]J{± Ù@¤/îˆ×éáêè×Ζý]lwõTÚC‡LMIs,µ%ú2ÙlÂ`—£ Ø´ø.âØêú[EK´Ý“¹í5¸ ’*N‹]Öv†–s³œ“4 «Õc6µºTýOÊ"j¥0ìôZ¬‹};ÄÒ0t$ë}u]µ4ønEf=ÛÛa÷¯`‰ 0oƒ×0;à`:·1p)”BâZØ¿ør6=1»2NQ숒¸¾µî¶<¨]çê0— li Zô%±®ül¯æM0 ëSŸ-ksWW| OÛo4ôÛy±šôºC'íB†á¤W!Í×8¦ü–Fy‰S]´õo¶F5oV°*c©¢Øn´Ž}Øž$†aüfž<Š4Ó·yå3ø¾ÒænžAʯèçÍ»i¾Â%%?/~'—o§z­(Ì_Áw–G«Uþ ÿÎÏ­Ï , Àæ«&‡[LÓx|β«ÕyôÊÀ§ÿOð}D<ëw~~x°8ÃÿG‡ß-ðÇYì[×vy„7ÄtB$p`é}¬4vÖ Ã²UÐD_•W­ƒ-‹€kÔüp•_àßì\¿Fæ$ò0ŸñY>›Ì¬¾ø?ÿâ˯¾þæÛï짬œ; ÀÑ ¸z áëqõD¾é¸£|~,dÞ ñÃÕäð¼VFlÒ®‘|j ×"¹]¡Ä@¾xT$ sã…±xšÍp Ôö·>n¨™ÏèçÝå¥^†éC77þÆwä!þÌðç©^dþZ@¿¶Ž±š‹ÒÏ¡ö°#U4úLÒ:SNô{¤q•-ò9»Î t ÊK¤•cÀ)ªLSh3ú×|[¯ ke–—§Ö™ßž<ÿxYN­S_–Ÿ6“ü”ÒãSzO»Š‚ ºÇWc•1\ž£0x¤ŽÎ‹Ü ½×Íü(dñ&‰£_á]dÌå!¶ö÷Qò°n„Ë7°[ŠŽOA¨õH+H¥$+^i{˜ŸÏ zÓhìöS\dz>Ï÷ògùõ¾ê~þ½gû÷PKH…IIE›yæ‰$§Êinvoke/vendor/yaml3/scanner.pyUT HWúWØWMXux öí=kWÜ8²ßó+r3Ý=iz ûšaCXBÈ,gòÚ@fÎÞ ¦Û€7n»×vØ™Üß~«J’]zÙnh’ì=ë…žî½ÙÝ9Øûy·Ÿ†Óh| “y4À¯vÞ¾Ø}yPU«^ˆŠOž¿Úùiu÷oow_îìV¥ÄëÛ¯_ï½üÑx+*>{þ껽5«éEYuUwºöæïª€úç§]üùóöó·»ð{ûùÞö~¿âöË¿¾zSÿ°ýcýÏþÎöómùqÌ’0N‡AQ^%ø¾¿‰ÂI0ΦÓ(-‹ N ùŠ6ãl‚”ȃi–GÁ$*Ã8)FPïÎÑQ˜$GGÁfð®'K÷†ús7ϳ¼÷þÎÓ<›£ÿ âé,ËËàE˜ˆ&ß~ñœJÉ"’ø²Ì·w°(¯oÔlÜ à™A¹ª8ÔO¢Ÿ¢+ñ FEÁIŒô)ø]§åQXâ€Gw¨Ü$: ŽŽâ4.ŽúE”œ3¥óéI”ƒ<úç<ΣÉ4‰.‡A§€Íq–̧€Î)tLö„0âKü_½ ‚ …ÔŸzj¾Òoýv¾à/ýƒè|è±»ð xÞðàƒ neee>Äaÿ+"f)DÝ|©JÝ öÊ .€=ŸÂØÊ󰬸*L'ÄvðçEœ$Áyø1 BbÂ,…Æ‹q”NB¤N N–ŸdÔä$ÎË«à"ƒq€¸ŸGã()SŸ¼zA Œ³ôc”—$@Î#)Ngó2˜„eD Þ¦12ùû&E„“I¼|û?bSÐÖ»GÅ|†,[Bk•çÙ¤`å%ògQô¡o® ªø†j§Ñ% mµ<‡…y8.«Ôõóè4¾ì'›ëgý¤®ìh0t擪ú=à¹pâ®Mhœf•gYœÂ[9cDÝ¿BÕ‹A & ]J‚8ͦ[:ïM2âØg€ëˆƒ:ÀˆÉÕçé8É ÙûµGÝè½ë‚ãS@îQ}Œ’`s3X;,‡ 4N’lüÁÒ—0˜‘Þ2¯¬ñ¶ŸÇE‰­ÂJë6+¥qm’/ÍÊà**ƒh—e49¦z¢ð=»=(áË–€lö,*ÇçG]ð+ÌËþ€zYá…÷ë"‚ŽÉÁ›<›ŸS{ÇgQyD%%:û{T†ðËÄÒa<ÏsŽ$pÒ2,c˜•„¸‘-˜ Øf°ºÎA¼ GÝÂYÙÆÚÏa‡' èy”„4¶¬QxKd³2$xèBü‡ôƒæ2wrEXêmõ°“ñ8,³œKšÝ˺Y³Ú\Z]]Õþ'dÅ7„þ¡•Ù¢^„¼”ö}#ø58ÕW* &øÄ þ"ˆNc­@°¬q§¸BÚL½…> .ÎáLÏS&´;ƒÑ§(·°º©!–,ó+Ä6 O-Œ3`…b–¢@¨õ6<8Ûgô)γyhŽ`Y‚Ž â°Ó³$¢¥ŠfôúÚÃß3a£s'L5D4)‚PŒZ±ç,+bä¯-øÓðŠkrD¯*0'ÑYœ¦8,)¯ÄšŽDgó”VÎÅÅ,é ‘ Ÿ>¨L‚üRì †zk§0<”cCbðcØ£:´\ ¹«ma ü±ZWÓ›ãtØs]æ¦Àiž‰®ˆÏÒø4†ŸšUá$‰Æ8PPÙŒzcN‡¸ú Z!-6ƒƒ|®ÉùŸ¢h|Žiå8E “Ï´È è/Ía`5l&̯F$™à;_É ¾$ÿ»“´ ä¾4Z+É\u1`¨;à:&%5'fõ¤Ò,A,Á¤˜ès·¿¸ºè“]5Î/bÐHÕJˆªýP©ô8tkˆ¿2b6¹RôQ`$Büë§;R]~=?Iâ±R^˜vL*–XT¤‚üíø<‹aB 8&v°²U¥Pñ(D ȉv„w´­«;xq#i°›iMŽv¢Á¢ÏÚ¨Æ"M­TU:À:½2|£).z¯Ã'Êyž ¾åï‘?D%Ü$y«øDP0G}Ö‹wkÚÀ®ækY¾cêR5>F oD ÃàuÞL.„IâzJ[/,ùÏy4>?-äèt,Õìô˜Ž£üü°Õ©›ÁzËaÎúkƒjÎåñG\níIguÝ@ƒêêÔNÄ ¶aÅ‘ümÔÐøOWÅdF uqBÓ#PóàpB«È>§ó…´!—`¶€4rI'Þ®²ýn ,ŠøÇXáÙ¦¾D4× Qã>uF¨r‹)­&¨-$ÕNÈË•b¬°³Vh}}­mj+=±†xÞd;j­ò¨%ߎš[Ñø”êo½Ãµž_&i3åj•”5§‘ þ}±äE´*nJ"»eVÔ=¼I6ž# ÁÕþªÝ¾¬£vš­0Ê·ôÐäêÇÈßBlç^Xd8xõôÕ†2ÃT& TžbÚEõ˜’Ìh?¶ûìYOWåmŸdÓ>YK­®Âê^€NMàŒÐ¶é 7 Êýºf¢q“fûòÕRÃÇ¡±“­q*vƒ KF KH¥°Ú~¹ü®•—I]V ›)J­OÃÙ »Þ¡ñ_»5.!vh»9NâÆ¦ß/8nÏT¶FÝÚð§ÅÆÜÖ.ð7ìÀ[vk‘ ùÚ;¾Ùƒª*èm=Ñ »»BÖš¦.lY] ¥¾­i*änRnêšݰ¥JíÍÊbŽUDQ‡…«µo[IJU}`SØT8GñM;\ªë^ÕÊðÌôn+P¨è†˜€ö”£²8“ÐÙãßÞ+-µÞÜ·5*a» <Ôßîãk·+@»›•¯ÎÉìèoý°×®‚(ú‘ÍO:5³ÒÚŒe4Šà¼(¥ÆîCÙN&¦Ü*õ\Ì*Ø>_d1}Ý‚6{¨aä7Ï#ü_ú‰¡)nÂ…OqExƶ®y‘î6\‘»A|§¼DºŠ¾2„fÓh¨õŸ•ÓlŽJ±R?ƒû¹°'!¬¬TƱôJ îCY w³hù骭ß~‹OÒ»ÕiÜ×Þ$hüEéÚFŒ‚íq9“äJß°Áv²WòmÛLs/\œÃÆD³ÌýÑ^!€œh)02bjP6‰t‹ïL]ÛCC«Y ©¦}ã,æ‚ô΂„Ï4&g•‘ØH Þk.Ý;¬î‘áëÕz…Ü&¼NqC¿ô‰"L§þA¼÷¦YËê *šdvˉÜÚ·GVqÛ(åš Ç4T™Uj^nؤ:¸™öÀ¨9 ™š¹á€Ó3 qŽ·Çhš%—oÆÀ!ûaÔ@PÌ¢1©ÜȺC^—•^íàÁzÊׄ>§¶£®õ4.“Dx§ãB¸'s#yÄÉ®y½`:£˜I¢ô &¬V ÖyŸ—A'á8œƒüp€œ)Yøù~Z:ÉɈSžã„fÎO“¸(ý3b° nEN"gÐÝMËZ\ˆô­…XŪ"(â1¡Øi…ÅBÊï±·¶/Ü ‹ÂDnKxþ¬Œ‰P¢‰å#º¾C6Å X« `DhEg=±ÂÎyeO«Ý(ϼ;Úªâ|¬ìø1­¼¸aýÁ†¡@Q{ƒ„çætžŽ·¦ÀÀ¹4ÁÛêÕ¾ òüÿÚÓ}ƒªk¡áñ­âhZ}U,âÆ¡òÕª¿òooò˜š&§s8KaQ%ôákZþ⼨¼!)ï ƒ‡S€A—–ó­ÜÐ<Å›ŒK ªe>¨ Š9lgÂ@¶Û~å¯XÀŒƒ£MÿXŽÆ4Û¸¶øF—p” ±]TLaPÇNVÑÕVôóçzi†Œl5PhKä*r‡¢‘_ÖQXv&œÍÐ~û³›NÈüß'‰":õ¬ '¾)[ã¥Òc.¢©ô§9§©Â½ìü#'´É.³wM¸0B…kr¶…$Ü ž¡5§ŽâªªZP£µnýUÊÍd¢ÇI†¶Ú„Ó¶Žù—%¸ö•ËU[e€«ámá®æs†S*dïÓˆ÷qÀ&‹èKO”Ž3œ›DýÇô.Euÿô~¤/î\þg­®7zaW× tàüçNš›/"LJè—t tê@ņi. P˜3”DÁ{§qqnEˈgWÈAíß5¦Æ¿1ay×Ç¡˜µ8»«:x¯C$˜ï{`áMwIÈsy+wOÿ©|UÏP?x6ծǺ®#V*¾ƒg1>ë¼&§µ0°ÉàÉJ¦aCp¨ŒP]]]íÝ*á¬Õq¡ÅOq±æ‘¨G-AP„ø]ýðÚ©ªÎ±5áúuëà ˜Í¼.¼ŸÉ¨tYþ¼Ý— Ùáp¿NK/„¶†¬ºÍœš°4ðøLQz7½†.CEf1I®-®¹ ˆ=>ã˜Õ51õhwQü^÷zñàgdeDZAägûÜàM¸ú 3u³Ü4™Ks†ö O+–bñ6$+77aÔkfc!Sþˆ ÓÌ|N6l””¬‘§Ñ‚L½j0õKý äå÷b/ú©+;Ųu®ÕâäJç/ÃÇ"bÅ¡c7K†á5$ÂgដTtšøKÒ§à.bW¨ÕÝhÂÈl¢<áÆÚIÓn’ Ma"cî´M´3ÌCÛšÊÐWS Éå)dœÖ–V…Ãm¶}ãÓÑ´d>+,$N¸FÕáDÕQÔáVüšM˜÷”'_Ù#Pow™CÞ8£z~åv+MÕY‡ñÅV|¼û3-¤ üàño0Êl “,pÉâ:Ç+WZ&0-¶Ob¶Ïj?}‘IþÓîÜ?E Íjuk«Oñ$Éè|JÇŽÆ´ÉÅ\žæü6ñ…Ï\¡ º}¬§Ð7C¶VYá?wÏꊕç´ò¡ÚÓ{ïTøøªÃÚ…T•Ä’eÈ0« ]BkŒ.')Øu¯ÜX2æÔã*]ðy N2KK\y¡oàЦ,„M%´uÕ`ºìâôè^\–0Ð O¶A¥KÝcë5&ý[?E¥¾˜¥°½E÷³`Žóhº…(¸žâÄDI)Fdò¡~Âôê"¼ :±ŒE8áÊ µB‡<:pJ ããt!Π7´\Ýk_Øñ¹æâŽOµÀÓ8^âñi[敘Zª$Y?ÅW\¦ßcºXÄV3ÌÒ¤I³‚OGU¤Âé Ô1Ê•d£M%©z²¨Z"š¿®jR5»ˆzByò3²÷"*Š8Ác«().fžÞnÖo§¹†‰Oóß¹€ø|wò\Ñ6ZØYm„ˆ"ŒPL¨DÉ’|n¬PC7F‹ J^è—1x|ÊÆÊÁöŸ%ÐÊMñaƒð!ƒÎ‰YÃW¹ü¦}agú”Èp³÷[϶䊓Y‹ÁylÃÑ‹‘+@dOÔ¹ÖLá"Ž»TTQÿ+5ëhaØihãЦ¨<»Öâ«Ñépس !§-gÅÁ¼”Ÿœâ²8èvËâ™5eÆc,<ý¢ž…0›x„CJëGoC¼­BD”¦UÜÂ1…Š“~È ÏÃô,b °dj"¬ÂCv|9Á(ŠÍ_u‚xPGtíØrÜ‘xòèdu–â õ<3ß„ÃB¢‚r6Ô»ÿ¡”£?«„QNø¶ŠY§0sä•h„=UAe ÒÍ`­Cgn G‡µ@ Ña A ú=êFo«×`Dláù°žÍoCvζ¿Ü|¼>Ó)í°¬1Ã.ò¦#€Í1ãöô3¸ãŠf¤áó—i–®Rö'v<ºG3-¿$1çgnV©ßãÊù©§Gÿ÷îáÛoðÇ·øã.þø <Æ  é¿Që>¾þ þ8f>Cž¥Xåq¢ÄÕxüýe_ªókB—Ö·Ð…wÓuhá…O"•aYc† /Pj¬¾8á«æ°N‹s®ø"LKËs‹ O"‘²“(D¸ŒF<·´•HÊà5øŽöF.[ÝÚ¾{ÿë§{ß|{÷·Ç‡@©û9vJRX<û÷¶ÂîY0Ôƒ¹_§jAоóŒP ÙÚè XúaDä*ƒ#;™¡p“Tœ1‘)QEzËàôzb4-5é)ËE[WpZ¥È:XDŠ®áƒc+Lv–‰´•“«Rea"ã01tjRj­ÔÓYt†“Ù£pÌ2{"¦Vɦ0ÑTœñ$b ¯íƒ žC5Á6:ÏD}¬LÝAÌ¥q“¬ò ˜Ìë ”÷Ô9±~©M¬â Px€Ö9¡cç8 ‘!µñØévp–e13c=ÏíAxR(g( ãhâH8CE†.[î°>ê£ ´j= ùJ1jw#Ô5ö…©ÀÓ}FçÞ¶Ÿ<¶“H8•øÂ:¢ Ï©€åÞëª] +±¸²ÀY‰‹ß±ž‹àX Ð>V‰›ñ Â‹–]YÎSj‰2$i'Hk›£ª‹ÀHÓl‚i~'ì4 ?BtIZ¡¡Ö ܉Ñ*lÙTqTÒÄ™8°U¥Ô!±Ó\¬ÑÀ¡2zÚäCaî9 ØmÕ·£ÞÚ±#$:Q°‘`3“ßÈ¢<U‹> /h+Iø(²èÚ¶q²Âíx]†:¹YK,d»êîª70ºã-ª–Þ¥#üÄlêuáQ2’« B P3|ÔSuꆮÂiÂZ“ꯣ9Âl»o Jx¶líAž-·&õ;TÂg ÃC]Yå¡TšÎáJe멪!œ+ì.Ÿaàô´øx[2’4ðUí]“ÙeÚ¼+A½2ÔFQ¢@oo­<ÚIJð³÷ƒ·Þæïþ[¼ ù»ù4G©Å­Äݳr·ôQŠ#ñÚØgu9__!{E#ŽSœ­T‡éÃdvžDe<¦$ Åäðg¥ƒ‰TÕB|ÝÏWœÀ(Ë–í‹VŸµI§îK1Æ®ñ®ùÑ»%ÙÔ¸ÊÞ>Sþ­Q)§¥¸ÍBŸjnQº¤ ×MEð iø,oû"œÇ)‡ µâ.¥®ý|…zg°OF‘0êôZ±®5$,qÒ‡ª8]ª¾žYÁ,‰r6ô‰Á†yæ äÁß’æE“dBôÍÇLÁxëh·ñÝ(`+«¶tZ«—Wû¨VXÅëè+AÇ¥ A:5¹¯AþÑ¿I뜓úF 74,IРöE߆–9»ÜãXÒÝʺl¤W5ÛÓÚêª4Üu ñg£Sò]w©wñV±>Ïã£ü«Y‘–J ߦêk<Œ2··ïìÌ _Z=‘æqTP¸)¼‹ºŽO ë¸Q±ð;ãhsÍ4ÃL²¨iEÞ(~[$r“[Y)ø1R{¡;*¡\.Y•§tHµÕµICÍ­Ûmßß8iÞ³¯òâ/º§rVFˆl4è_dæjiÃÔ ÁXÑ€è]¯ †Îâu²F b—h‘#Æ! PøE¶÷wööð ¡ž ¶“)­¾ð29+âH+/n=ê[íT² HUA"fOg,{ÚýÇ6â ip¿@!/ X_ÓŽþsGœþÌ­áûOèýÿ„ÕFWQ§$XÀ®É¢‚¼å[ë Ê®›¤E©òÈ [µ×Ðìò=4ÏÍO]Rúàº'{bÚg»b¼Ü„'AÄò uX¸ñašÜãë*t4¦‰uNH≴j ˆBzïn¯[ëæÊP‰z]”Î µ³ü€ø¼/¬ãã¿wÄÙ¶åS)AÖßÒ€OËò€OÕ¬‰OèuÝ-»Ûží¹Úg6ó¿ÛÛçí > ¾ËëÍÃÏ¿íê6Uo²áRËbe¨¹–„Ï„÷jùÞpÊ@Aï$ïµ÷Ûè8—âÆf­ªœÖ<>Ÿ§äÕÝ+5­0|¨¥ã(Îa`ÝéÚÕeSqb²£NYÞV suV½ÂicòÕò9'Y·ŸV§È±ïÖ‡¤ÞŸe*¤MÄa¤\4•WFð8^’áÊ@û`“•z¬yn9­V$yû‡^QiáÂK `hq¶­j¸fðƒêì×½â 4-(uõªêƒj«ëή·wW”ì›Í×Ûa”='SÆ”%Y„®ÓÌó;ÌŠQ‡XËј5w]×LŠ)4Š.1c_ôXG-î‘1ÑYŠùô(ÞRº•˜µ gT´6dF—5{úÂæÿŽŠ†ÄO*øLîø4n2,zy¬Ke|ìÀù6ªZø¹¼Ma\å< Ë(¹’­Cîèpîô$>›gsã4<ÕvÀS¢Ë{”O¼n¯ 6Õ\º•”ï84ŽlYÚÃ2î“øP‹CM$quëorŸ-(â.‚Î[°¼ÛG©Ýš‘]¹© £÷°e–Úý¦E¯6&XðÅЧð@ÊŠ„pŠ.C ã2oÏ¡ö›6ê8€àtV‰æ=¾[7´ôô(Q•w“¤\mî5@ëÅ @ì Øb°ƒ‡È4 J•v°I)#Ȉ3Ò‹œ‚½¡+ÔÁº¬ ¬‡2Sf–æ·Úö¹O"N( ½ÞèYœöÀÁPô×­‘:n­#´AQÕÔ­¥9¸%fÌH?¦üiZ6#ÈÕV ¦®·•ìκÐ1Ø’Õ°·¿ Û/oßõþ¯­?üÝïÿðÇ?}ïÜ"3Ä «{|nOM/´Ï‚©§ÓVŒëCÍ'øÔ{3íºŠúD¡9Š\_ýoßÖMJâ,Ðl^ñ⸠¿p{[x½-œ^ÇT…OGv¶¦cýÙ7%tÿ´Ä§iýo˜ž cúüvŽ™ f€jˆDv3Ë tìȉÿx©¿¨—úÚ<ÕÕQí¹ô¶ÂÁµÔ^ÄMªÞ¯kp‚êýCm¼‡+²Ÿ+ÚÛCùö¿}YÁøþüýQõ~[ƒý\½§%ŠyÍ¿(ü|Ò·óêé®A±KQë!4¯~Ï^½¯¾×€º–Îábqðù1Ñm3ßuý4@³ÃÃEïø6 •]ñmõ8:ûrðiõç´™p€RÆË”dëîõ åb} >´õ[ä‘kìèCS#†Žô½T 6ôÏD ˆyÔÆîŽË4× lS_]9\ì |+ê%úÅP¨C†vñ¬øê¾Ÿ¿·›ìÐmf ã°ID¸;TÍ)«hÍJ;hé—§O47¶0Õ¨ËÜ€ÏbG]ä™4ÅÑ”ÎâœRºO2‘ÿ–Ýãž’$¾Ž dvXÍj¶$úÐÐißÖi äòñZj, Ú^ï<•_'tų çe9Ûøî»Ùæ“eùÙwñ‡ø;Lr·“%Yº—â]º;RJóbÅV=åìu'ÙüB½õæ¶ç%©¡²K‹ ïè¨Ô·FõŠ«6~ʽܠ¯“d`P[bÎ3›„ŠËqê`_"]œ%(H»3Ò°À=æËPÃ])âB]í"ãt.Ž^žd¸bRY˜]úåúÒú¼›Ú:‰Å­pò&6 «áõŽˆit¦*X8=î.u;þÕlÆoië›ìVˆÝ¿ÇƆ&‹{ÿN7ÿ^?rGŽ´K´C…á&·Ï'z¾hè>]hÅFr#š©çÆ´Sƒ†ÿVæ€Î$ÂRM)옰Ýf"„ëKîg¸ÈeSJbNñÂ0®"K‹¡D•Fì|zG¨2X%ní¨›Å(؃??Öׯ¥º§³«à»ë8ýÝIy»^®ºzáz)ìÖ}Cr$ñd[~j|Ò£°\÷¼ŸLø,O_„žøÜ„¦>´Ü8{JK–5°” eT »çõ^ôpNv;Tç‰~À=ĪôA`xò8OÕRAn„Øõ&‹Þväžâø—˜ãPx@Ú|n¦xW⸃RD¤„;«t'¢e’CŠQ6ûûjLÑ—*u³qu•äARú™;vjÁ¸£Gû>ç j†J»‹|˜Îa„0© …ÄNPëZ|L¡¸L-~¥Q¬úL›ËA2$Žˆì™|RF+Õ2âY˦ÓkZèÔ™Ó|’ú¶®‹aBë<ÖYã3aXÚÕuš oyÏ Ó^úoò˜=þ€Î„V,Þ>™¶•ßÅ{g j±ãè›Ú°úWnÁÒ¿‡¬˜ìÜ»4O[OTl+ýâ6àÔ8(c··C”ü¿‡l¥âŒMä øÀ#˧ÎÐ_ª&N0/Ѐ҄Ͼ0“—tâi2žÿPKH…IIAû– 0 invoke/vendor/yaml3/tokens.pyUT HWúWØWMXux öÍTËjã0Ýû+%Ø7P&3´´³ÚY¤Ì&¡Ø7®[J%¹% óŸ­å¤ÐBµˆßsνçꤑÝó°ˆo!UñE€ôÈ`‹0¦Œ*Œ# Å6AR¡pIÄ.AÀ2»ªÑfмàE05d i–½œö¢ÎÙS'J º©HÍZíà€¶\ 3SæT1Îhª«m)Í [ĸ2X“Y¾Põ…6k¯GÌ%*Š»ˆÈ«˜2™Ã…óGNY´ gr1!š¡HK'(e4êféOqü¦”^Í]¾u‡ *Á–Žf2¶Úµ7»Që#%`œteÅApæöñê÷­ÛJû«»wfýg¦ðo›ƒÄEïßà p¬k*ô¾Ógr{Ô¬Ah’çp˜ªôLŠ N;)† ÅÍ4 X ±ó®Æ)OmË–†êw[£\†^Ÿêï7,;ÎÕ©;æR åtVi1mΣwpqÇtíuXÊ3ÊrúЋÚ¸à–CÏS½ª:uUðt·„§JËÁtÃ6ªËpØ×›e…nÉ~¯K:E§tÐQ™)Ž=ðð³à/'YX (§ûw4ÉD}ë±ø-þ| -è¹£>ØÅë.*á•<Ô9 MZèeA‰ôî 1QÿMzÇSõ‰/Ò%K¸ð{°á¯nâžä^Šä_½üeJ âßiÃGMì BÙ¸óõPÀè;<áË*ê?ãõ¶UY=ÁPKu‹IÒJ¤Í)éinvoke/watchers.pyUT =WMXØWMXux öXÁrÛ6½û+Pe¦–R•“\Ý&3n›vr‰;µÓ⌑+ 5EpвšÉ¿÷-‚ l'ú`Q°Ø}ûöíÒj×iã„¡žÜÖ¬U{sr²1z' º¯¨sJ·VÄ-íð•Þiw^ñ"Õ'''U#­—ÇwIWmÉÌ“±¢Ñ•lg'?³ÙÌž‹pf¿Õ–„í×þ+Y±“!+'t+,~y£¢–N ïövFWÄ»‹oì2;Þ['è~«ÖŠã!±ÑM£÷pCœÿþö¶)ÊDM¦Ä²RTº­ 9òÖè^¢íç¢,W+Õ*·Z•¥P›yÙQs}'œ$«m a‰[u³ÒŠÞö²Yz3"ñ¹Þ<Ï¿[œhj±&QIC›¾a«;yK°@BoDiû޽ÝXŠöd:£¤ƒ«ÑSØÜ)W¤ÏZ§ ¬÷Æà‘Ãuø´lš—#ÆkâÔë{ŸÅzÉaHñ¾U•®ýFlÁÛÚ'J{rÈP¾Þ´XÊ‘‘ë†Ʀǭ˜3èȱ·}C-ö;üÉÄÃR¨‚ Ñ8¤JÓX@­•åAàú±Ÿ˜}]–‹%RHÆ^5MH 0Ýå.cŠ`ÇlDâ<⑦ªízó^¢ÕŽÎÎâÜ;áw™'îW­è$—(Ñz`Ù‘pFV·7ÛWÛdÑ× Q嘜È,À!(*X²v¯áT­êöÔ <ߊo£9ZÆ0YB$»Î›‘Ì4ÉU‹¥Á h}{KÔñeÙI«.´­¢;v5ÙÚ+2«Ýü¾‹MßV^þ8å‘`‘3!(¨Z†‰Àmm2·öÔ¯ÊSOÅSá¶YÚ€m­{穈GàÂø[B¤à wÛb">5m"ÒsKÍf `1†3ìôRÔ¨,Ã.ˆ‹ÑRtš‹Heìg|MÈ*þ9c§v"ã+쌗ùK@X¯qì._ç¶Ê…iò•º¦Õú{†ÒHL~YðÅÙŸÔåÊ uÃ5 è l:,ŠGÑ0RA“ ÿo™"; @õ&ejI]çNÀÆä»é í¸ïÌCb¯†À§n]m2ùLùôEÅ=ÒZAP»²Œú¶:±t‘êÇ)øL\]ürqÙ#îŪ µqKAtù4æñJñj@wº<‡õáqºA!‹÷X}12$Zí<ÏíDR2Âø“+|3OPæ7n„`÷Ì’4H'ppWµÉ`o½Œ³µ²¹•wJ›,Mï¹xNðÒÈõn2aùÛË6ä‡{ÓØj‡ÊJÖx2 SCsøª¢€ÏAÊ˜Š¡ Ëa2)‹§L d÷6†°™J Œ'ŽÈ†Ó-{õ1ä‹×Ôºç^¬Y%Їjy¬‘Ì1©OñÝÅÕ›³0Â@™x”CóúŒ‡ À·¼—ñªÏëë—)Fé2{@›–ÂW52÷«T l%õ(²,x »`¹ }"Íu{àq§÷CôÜ’ï—wZa¢À0™E4Pú†ãù›Ñ5íli¿ÂÆpÛ¿ãìcæÓ¥ÏÆÃÊhËÒÚµå.í°ú2+6°„.:O¥Â÷°Â—‹Ìúû®æi ÝFŸazO§wÍÕcH›á†©JÙ'BŒÏâ;ÑP;çëãÍq²ǺÿòñL¼õã,ÈtGF 1.’®p‚+‰OpÁÛºò¥–©SeæšQ»F¡‰Š/.Å©åt1<ŒÌK û˜fóôtÔ³ÿ”xÝ€nðIÞÒ|wá7#Ù å3N¶˜]4¢ªÜƒÑèŠ{cpAÖ5¿ @'x¤j©AOH­1(ÈøÞ?T”öQ[Bƒ JY<ò¦Z21P•Á¹¨„ÃÞØlzŸ™ñµèáèøµ‘`™lfIðopóc¨CòE2ùp¾˜öŸÁ2—aîxÚ°Á ½¡Õب&ËP5HÑ+ä¼aüW.ÿÄ}†Â°4 )Á¾û98«™ÔG¿øpq¸2»ñ¼‚‚æèNQݸtR >ŒŸ|pVËTòÒ ¡v&è¦#¹c~Øüêáå‡õhÇ õEä†i+yBAwîÇ9¶È%*K:œ–-¿Ö¼³óqzµ¾‡™ëÙ§Ÿ¯gÃ…~ªüôòóù›Y ô4àD€D°Iè‹ÉæPBÐþäX\ð{IÀ ŽÜxj?Iµàøÿü¶˜‡þø9aè•é)çÃTðzˆ9ˆ¸>0%˜âX±“Ðý PKH…IIÙâö¢Þ __main__.pyUT HWúWHWúWux ö5Á‚0 †ï{Š —a<ŽÏFÏ Ê€Fבmy{™Boý¿¯ýã]>:+Ĺ⠆Ù÷†E é>…§iˆ»î·sz ‰ =ëÁ¸l¨}¿­nvâR]+(7’5d¹ÖJ"¶ôVˆ‰X¤?"vÊzy<@¸I„ ƒe ¢®‰£BÀ2­5ˆ'óRY°¶Öt¶Ö?'¤'ïinvoke/tasks.pyUT´VMXux öPKÁt‹IPÔ‹aŒƒ¤.invoke/util.pyUT¹VMXux öPK H…IIíAinvoke/vendor/UTHWúWux öPK H…II¤Jinvoke/vendor/__init__.pyUTHWúWux öPK H…IIíAinvoke/vendor/fluidity/UTHWúWux öPKH…IIžJ˜qiÄ"¤îinvoke/vendor/fluidity/__init__.pyUTHWúWux öPKH…IIÃòRh‡)¤³invoke/vendor/fluidity/backwardscompat.pyUTHWúWux öPKH…IIeR9¤~invoke/vendor/fluidity/LICENSEUTHWúWux öPKH…IIª°¯ î!!¤Winvoke/vendor/fluidity/machine.pyUTHWúWux öPK H…IIíAÌinvoke/vendor/lexicon/UTHWúWux öPKH…IISýÁJö1!¤invoke/vendor/lexicon/__init__.pyUTHWúWux öPKH…IIòÎÊp C #¤minvoke/vendor/lexicon/alias_dict.pyUTHWúWux öPKH…IIqõüŸL'¤Ó"invoke/vendor/lexicon/attribute_dict.pyUTHWúWux öPKH…II×—¯§"¤Ó#invoke/vendor/lexicon/LICENSEUTHWúWux öPKãt‹I™XMZc’u¤Ñ&invoke/vendor/six.pyUTúVMXux öPK H…IIíA‚Dinvoke/vendor/yaml2/UTHWúWux öPKH…II^çf'0&¤ÐDinvoke/vendor/yaml2/__init__.pyUTHWúWux öPKH…IIÖ#<9¤PLinvoke/vendor/yaml2/composer.pyUTHWúWux öPKH…IIÜ‚yŠü9b"¤åPinvoke/vendor/yaml2/constructor.pyUTHWúWux öPKH…IIrmÕìÃÚ ¤=binvoke/vendor/yaml2/cyaml.pyUTHWúWux öPKH…II¼hŒekŸ ¤Vdinvoke/vendor/yaml2/dumper.pyUTHWúWux öPKH…IIúVؼ˜"©¤finvoke/vendor/yaml2/emitter.pyUTHWúWux öPKH…IIáre·êÿ ¤invoke/vendor/yaml2/error.pyUTHWúWux öPKH…IIÞœ ¤H„invoke/vendor/yaml2/events.pyUTHWúWux öPKH…II+»•Ül¤®†invoke/vendor/yaml2/loader.pyUTHWúWux öPKH…IIµ ‚¬‡ ¤á‡invoke/vendor/yaml2/nodes.pyUTHWúWux öPKH…II·6ǶÆc¤¾‰invoke/vendor/yaml2/parser.pyUTHWúWux öPKH…IIœ:È#bZ¤Ë˜invoke/vendor/yaml2/reader.pyUTHWúWux öPKH…II-ŠIàT êD"¤„ invoke/vendor/yaml2/representer.pyUTHWúWux öPKH…II7¦b| #¤4®invoke/vendor/yaml2/resolver.pyUTHWúWux öPKH…IIW‰·$–ͤ ·invoke/vendor/yaml2/scanner.pyUTHWúWux öPKH…II^5šK!¤Üinvoke/vendor/yaml2/serializer.pyUTHWúWux öPKH…IIAû– 0 ¤ àinvoke/vendor/yaml2/tokens.pyUTHWúWux öPK H…IIíA”âinvoke/vendor/yaml3/UTHWúWux öPKH…II8aÿX‡%¤ââinvoke/vendor/yaml3/__init__.pyUTHWúWux öPKH…IIÇj,\)¤Jêinvoke/vendor/yaml3/composer.pyUTHWúWux öPKH…IIvë<ÐÒc"¤Ìîinvoke/vendor/yaml3/constructor.pyUTHWúWux öPKH…IIÿ’kCÃÞ ¤øÿinvoke/vendor/yaml3/cyaml.pyUTHWúWux öPKH…IIŸó¦ßl£ ¤invoke/vendor/yaml3/dumper.pyUTHWúWux öPKH…II¦ã‘sʧ¤Ôinvoke/vendor/yaml3/emitter.pyUTHWúWux öPKH…IIHVùÃÙå ¤Ÿinvoke/vendor/yaml3/error.pyUTHWúWux öPKH…IIÞœ ¤Î!invoke/vendor/yaml3/events.pyUTHWúWux öPKH…II`Õ¬áÝr¤4$invoke/vendor/yaml3/loader.pyUTHWúWux öPKH…IIµ ‚¬‡ ¤h%invoke/vendor/yaml3/nodes.pyUTHWúWux öPKH…IIûI@¡—c¤E'invoke/vendor/yaml3/parser.pyUTHWúWux öPKH…II °sMmƤ=6invoke/vendor/yaml3/reader.pyUTHWúWux öPKH…II­±–Eæ Ø4"¤>invoke/vendor/yaml3/representer.pyUTHWúWux öPKH…IIõ îï]t"¤CIinvoke/vendor/yaml3/resolver.pyUTHWúWux öPKH…IIE›yæ‰$§Ê¤ùQinvoke/vendor/yaml3/scanner.pyUTHWúWux öPKH…II_= (3,) PY2 = not PY3 string_types = str, text_type = str getcwdu = os.getcwd def surrogate_escape(error): """ Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only. """ chars = error.object[error.start:error.end] assert len(chars) == 1 val = ord(chars) val += 0xdc00 return __builtin__.unichr(val), error.end if PY2: import __builtin__ string_types = __builtin__.basestring, text_type = __builtin__.unicode getcwdu = os.getcwdu codecs.register_error('surrogateescape', surrogate_escape) @contextlib.contextmanager def io_error_compat(): try: yield except IOError as io_err: # On Python 2, io.open raises IOError; transform to OSError for # future compatibility. os_err = OSError(*io_err.args) os_err.filename = getattr(io_err, 'filename', None) raise os_err ############################################################################## __all__ = ['Path', 'CaseInsensitivePattern'] LINESEPS = ['\r\n', '\r', '\n'] U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029'] NEWLINE = re.compile('|'.join(LINESEPS)) U_NEWLINE = re.compile('|'.join(U_LINESEPS)) NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern)) U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern)) try: import pkg_resources __version__ = pkg_resources.require('path.py')[0].version except Exception: __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown' class TreeWalkWarning(Warning): pass # from jaraco.functools def compose(*funcs): compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs)) return functools.reduce(compose_two, funcs) def simple_cache(func): """ Save results for the :meth:'path.using_module' classmethod. When Python 3.2 is available, use functools.lru_cache instead. """ saved_results = {} def wrapper(cls, module): if module in saved_results: return saved_results[module] saved_results[module] = func(cls, module) return saved_results[module] return wrapper class ClassProperty(property): def __get__(self, cls, owner): return self.fget.__get__(None, owner)() class multimethod(object): """ Acts like a classmethod when invoked from the class and like an instancemethod when invoked from the instance. """ def __init__(self, func): self.func = func def __get__(self, instance, owner): return ( functools.partial(self.func, owner) if instance is None else functools.partial(self.func, owner, instance) ) class Path(text_type): """ Represents a filesystem path. For documentation on individual methods, consult their counterparts in :mod:`os.path`. Some methods are additionally included from :mod:`shutil`. The functions are linked directly into the class namespace such that they will be bound to the Path instance. For example, ``Path(src).copy(target)`` is equivalent to ``shutil.copy(src, target)``. Therefore, when referencing the docs for these methods, assume `src` references `self`, the Path instance. """ module = os.path """ The path module to use for path operations. .. seealso:: :mod:`os.path` """ def __init__(self, other=''): if other is None: raise TypeError("Invalid initial value for path: None") @classmethod @simple_cache def using_module(cls, module): subclass_name = cls.__name__ + '_' + module.__name__ if PY2: subclass_name = str(subclass_name) bases = (cls,) ns = {'module': module} return type(subclass_name, bases, ns) @ClassProperty @classmethod def _next_class(cls): """ What class should be used to construct new instances from this class """ return cls @classmethod def _always_unicode(cls, path): """ Ensure the path as retrieved from a Python API, such as :func:`os.listdir`, is a proper Unicode string. """ if PY3 or isinstance(path, text_type): return path return path.decode(sys.getfilesystemencoding(), 'surrogateescape') # --- Special Python methods. def __repr__(self): return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__()) # Adding a Path and a string yields a Path. def __add__(self, more): try: return self._next_class(super(Path, self).__add__(more)) except TypeError: # Python bug return NotImplemented def __radd__(self, other): if not isinstance(other, string_types): return NotImplemented return self._next_class(other.__add__(self)) # The / operator joins Paths. def __div__(self, rel): """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) Join two path components, adding a separator character if needed. .. seealso:: :func:`os.path.join` """ return self._next_class(self.module.join(self, rel)) # Make the / operator work even when true division is enabled. __truediv__ = __div__ # The / operator joins Paths the other way around def __rdiv__(self, rel): """ fp.__rdiv__(rel) == rel / fp Join two path components, adding a separator character if needed. .. seealso:: :func:`os.path.join` """ return self._next_class(self.module.join(rel, self)) # Make the / operator work even when true division is enabled. __rtruediv__ = __rdiv__ def __enter__(self): self._old_dir = self.getcwd() os.chdir(self) return self def __exit__(self, *_): os.chdir(self._old_dir) @classmethod def getcwd(cls): """ Return the current working directory as a path object. .. seealso:: :func:`os.getcwdu` """ return cls(getcwdu()) # # --- Operations on Path strings. def abspath(self): """ .. seealso:: :func:`os.path.abspath` """ return self._next_class(self.module.abspath(self)) def normcase(self): """ .. seealso:: :func:`os.path.normcase` """ return self._next_class(self.module.normcase(self)) def normpath(self): """ .. seealso:: :func:`os.path.normpath` """ return self._next_class(self.module.normpath(self)) def realpath(self): """ .. seealso:: :func:`os.path.realpath` """ return self._next_class(self.module.realpath(self)) def expanduser(self): """ .. seealso:: :func:`os.path.expanduser` """ return self._next_class(self.module.expanduser(self)) def expandvars(self): """ .. seealso:: :func:`os.path.expandvars` """ return self._next_class(self.module.expandvars(self)) def dirname(self): """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """ return self._next_class(self.module.dirname(self)) def basename(self): """ .. seealso:: :attr:`name`, :func:`os.path.basename` """ return self._next_class(self.module.basename(self)) def expand(self): """ Clean up a filename by calling :meth:`expandvars()`, :meth:`expanduser()`, and :meth:`normpath()` on it. This is commonly everything needed to clean up a filename read from a configuration file, for example. """ return self.expandvars().expanduser().normpath() @property def namebase(self): """ The same as :meth:`name`, but with one file extension stripped off. For example, ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``, but ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``. """ base, ext = self.module.splitext(self.name) return base @property def ext(self): """ The file extension, for example ``'.py'``. """ f, ext = self.module.splitext(self) return ext @property def drive(self): """ The drive specifier, for example ``'C:'``. This is always empty on systems that don't use drive specifiers. """ drive, r = self.module.splitdrive(self) return self._next_class(drive) parent = property( dirname, None, None, """ This path's parent directory, as a new Path object. For example, ``Path('/usr/local/lib/libpython.so').parent == Path('/usr/local/lib')`` .. seealso:: :meth:`dirname`, :func:`os.path.dirname` """) name = property( basename, None, None, """ The name of this file or directory without the full path. For example, ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'`` .. seealso:: :meth:`basename`, :func:`os.path.basename` """) def splitpath(self): """ p.splitpath() -> Return ``(p.parent, p.name)``. .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split` """ parent, child = self.module.split(self) return self._next_class(parent), child def splitdrive(self): """ p.splitdrive() -> Return ``(p.drive, )``. Split the drive specifier from this path. If there is no drive specifier, :samp:`{p.drive}` is empty, so the return value is simply ``(Path(''), p)``. This is always the case on Unix. .. seealso:: :func:`os.path.splitdrive` """ drive, rel = self.module.splitdrive(self) return self._next_class(drive), rel def splitext(self): """ p.splitext() -> Return ``(p.stripext(), p.ext)``. Split the filename extension from this path and return the two parts. Either part may be empty. The extension is everything from ``'.'`` to the end of the last path segment. This has the property that if ``(a, b) == p.splitext()``, then ``a + b == p``. .. seealso:: :func:`os.path.splitext` """ filename, ext = self.module.splitext(self) return self._next_class(filename), ext def stripext(self): """ p.stripext() -> Remove one file extension from the path. For example, ``Path('/home/guido/python.tar.gz').stripext()`` returns ``Path('/home/guido/python.tar')``. """ return self.splitext()[0] def splitunc(self): """ .. seealso:: :func:`os.path.splitunc` """ unc, rest = self.module.splitunc(self) return self._next_class(unc), rest @property def uncshare(self): """ The UNC mount point for this path. This is empty for paths on local drives. """ unc, r = self.module.splitunc(self) return self._next_class(unc) @multimethod def joinpath(cls, first, *others): """ Join first to zero or more :class:`Path` components, adding a separator character (:samp:`{first}.module.sep`) if needed. Returns a new instance of :samp:`{first}._next_class`. .. seealso:: :func:`os.path.join` """ if not isinstance(first, cls): first = cls(first) return first._next_class(first.module.join(first, *others)) def splitall(self): r""" Return a list of the path components in this path. The first item in the list will be a Path. Its value will be either :data:`os.curdir`, :data:`os.pardir`, empty, or the root directory of this path (for example, ``'/'`` or ``'C:\\'``). The other items in the list will be strings. ``path.Path.joinpath(*result)`` will yield the original path. """ parts = [] loc = self while loc != os.curdir and loc != os.pardir: prev = loc loc, child = prev.splitpath() if loc == prev: break parts.append(child) parts.append(loc) parts.reverse() return parts def relpath(self, start='.'): """ Return this path as a relative path, based from `start`, which defaults to the current working directory. """ cwd = self._next_class(start) return cwd.relpathto(self) def relpathto(self, dest): """ Return a relative path from `self` to `dest`. If there is no relative path from `self` to `dest`, for example if they reside on different drives in Windows, then this returns ``dest.abspath()``. """ origin = self.abspath() dest = self._next_class(dest).abspath() orig_list = origin.normcase().splitall() # Don't normcase dest! We want to preserve the case. dest_list = dest.splitall() if orig_list[0] != self.module.normcase(dest_list[0]): # Can't get here from there. return dest # Find the location where the two paths start to differ. i = 0 for start_seg, dest_seg in zip(orig_list, dest_list): if start_seg != self.module.normcase(dest_seg): break i += 1 # Now i is the point where the two paths diverge. # Need a certain number of "os.pardir"s to work up # from the origin to the point of divergence. segments = [os.pardir] * (len(orig_list) - i) # Need to add the diverging part of dest_list. segments += dest_list[i:] if len(segments) == 0: # If they happen to be identical, use os.curdir. relpath = os.curdir else: relpath = self.module.join(*segments) return self._next_class(relpath) # --- Listing, searching, walking, and matching def listdir(self, pattern=None): """ D.listdir() -> List of items in this directory. Use :meth:`files` or :meth:`dirs` instead if you want a listing of just files or just subdirectories. The elements of the list are Path objects. With the optional `pattern` argument, this only lists items whose names match the given pattern. .. seealso:: :meth:`files`, :meth:`dirs` """ if pattern is None: pattern = '*' return [ self / child for child in map(self._always_unicode, os.listdir(self)) if self._next_class(child).fnmatch(pattern) ] def dirs(self, pattern=None): """ D.dirs() -> List of this directory's subdirectories. The elements of the list are Path objects. This does not walk recursively into subdirectories (but see :meth:`walkdirs`). With the optional `pattern` argument, this only lists directories whose names match the given pattern. For example, ``d.dirs('build-*')``. """ return [p for p in self.listdir(pattern) if p.isdir()] def files(self, pattern=None): """ D.files() -> List of the files in this directory. The elements of the list are Path objects. This does not walk into subdirectories (see :meth:`walkfiles`). With the optional `pattern` argument, this only lists files whose names match the given pattern. For example, ``d.files('*.pyc')``. """ return [p for p in self.listdir(pattern) if p.isfile()] def walk(self, pattern=None, errors='strict'): """ D.walk() -> iterator over files and subdirs, recursively. The iterator yields Path objects naming each child item of this directory and its descendants. This requires that ``D.isdir()``. This performs a depth-first traversal of the directory tree. Each directory is returned just before all its children. The `errors=` keyword argument controls behavior when an error occurs. The default is ``'strict'``, which causes an exception. Other allowed values are ``'warn'`` (which reports the error via :func:`warnings.warn()`), and ``'ignore'``. `errors` may also be an arbitrary callable taking a msg parameter. """ class Handlers: def strict(msg): raise def warn(msg): warnings.warn(msg, TreeWalkWarning) def ignore(msg): pass if not callable(errors) and errors not in vars(Handlers): raise ValueError("invalid errors parameter") errors = vars(Handlers).get(errors, errors) try: childList = self.listdir() except Exception: exc = sys.exc_info()[1] tmpl = "Unable to list directory '%(self)s': %(exc)s" msg = tmpl % locals() errors(msg) return for child in childList: if pattern is None or child.fnmatch(pattern): yield child try: isdir = child.isdir() except Exception: exc = sys.exc_info()[1] tmpl = "Unable to access '%(child)s': %(exc)s" msg = tmpl % locals() errors(msg) isdir = False if isdir: for item in child.walk(pattern, errors): yield item def walkdirs(self, pattern=None, errors='strict'): """ D.walkdirs() -> iterator over subdirs, recursively. With the optional `pattern` argument, this yields only directories whose names match the given pattern. For example, ``mydir.walkdirs('*test')`` yields only directories with names ending in ``'test'``. The `errors=` keyword argument controls behavior when an error occurs. The default is ``'strict'``, which causes an exception. The other allowed values are ``'warn'`` (which reports the error via :func:`warnings.warn()`), and ``'ignore'``. """ if errors not in ('strict', 'warn', 'ignore'): raise ValueError("invalid errors parameter") try: dirs = self.dirs() except Exception: if errors == 'ignore': return elif errors == 'warn': warnings.warn( "Unable to list directory '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) return else: raise for child in dirs: if pattern is None or child.fnmatch(pattern): yield child for subsubdir in child.walkdirs(pattern, errors): yield subsubdir def walkfiles(self, pattern=None, errors='strict'): """ D.walkfiles() -> iterator over files in D, recursively. The optional argument `pattern` limits the results to files with names that match the pattern. For example, ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp`` extension. """ if errors not in ('strict', 'warn', 'ignore'): raise ValueError("invalid errors parameter") try: childList = self.listdir() except Exception: if errors == 'ignore': return elif errors == 'warn': warnings.warn( "Unable to list directory '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) return else: raise for child in childList: try: isfile = child.isfile() isdir = not isfile and child.isdir() except: if errors == 'ignore': continue elif errors == 'warn': warnings.warn( "Unable to access '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) continue else: raise if isfile: if pattern is None or child.fnmatch(pattern): yield child elif isdir: for f in child.walkfiles(pattern, errors): yield f def fnmatch(self, pattern, normcase=None): """ Return ``True`` if `self.name` matches the given `pattern`. `pattern` - A filename pattern with wildcards, for example ``'*.py'``. If the pattern contains a `normcase` attribute, it is applied to the name and path prior to comparison. `normcase` - (optional) A function used to normalize the pattern and filename before matching. Defaults to :meth:`self.module`, which defaults to :meth:`os.path.normcase`. .. seealso:: :func:`fnmatch.fnmatch` """ default_normcase = getattr(pattern, 'normcase', self.module.normcase) normcase = normcase or default_normcase name = normcase(self.name) pattern = normcase(pattern) return fnmatch.fnmatchcase(name, pattern) def glob(self, pattern): """ Return a list of Path objects that match the pattern. `pattern` - a path relative to this directory, with wildcards. For example, ``Path('/users').glob('*/bin/*')`` returns a list of all the files users have in their :file:`bin` directories. .. seealso:: :func:`glob.glob` """ cls = self._next_class return [cls(s) for s in glob.glob(self / pattern)] # # --- Reading or writing an entire file at once. def open(self, *args, **kwargs): """ Open this file and return a corresponding :class:`file` object. Keyword arguments work as in :func:`io.open`. If the file cannot be opened, an :class:`~exceptions.OSError` is raised. """ with io_error_compat(): return io.open(self, *args, **kwargs) def bytes(self): """ Open this file, read all bytes, return them as a string. """ with self.open('rb') as f: return f.read() def chunks(self, size, *args, **kwargs): """ Returns a generator yielding chunks of the file, so it can be read piece by piece with a simple for loop. Any argument you pass after `size` will be passed to :meth:`open`. :example: >>> hash = hashlib.md5() >>> for chunk in Path("path.py").chunks(8192, mode='rb'): ... hash.update(chunk) This will read the file by chunks of 8192 bytes. """ with self.open(*args, **kwargs) as f: for chunk in iter(lambda: f.read(size) or None, None): yield chunk def write_bytes(self, bytes, append=False): """ Open this file and write the given bytes to it. Default behavior is to overwrite any existing file. Call ``p.write_bytes(bytes, append=True)`` to append instead. """ if append: mode = 'ab' else: mode = 'wb' with self.open(mode) as f: f.write(bytes) def text(self, encoding=None, errors='strict'): r""" Open this file, read it in, return the content as a string. All newline sequences are converted to ``'\n'``. Keyword arguments will be passed to :meth:`open`. .. seealso:: :meth:`lines` """ with self.open(mode='r', encoding=encoding, errors=errors) as f: return U_NEWLINE.sub('\n', f.read()) def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False): r""" Write the given text to this file. The default behavior is to overwrite any existing file; to append instead, use the `append=True` keyword argument. There are two differences between :meth:`write_text` and :meth:`write_bytes`: newline handling and Unicode handling. See below. Parameters: `text` - str/unicode - The text to be written. `encoding` - str - The Unicode encoding that will be used. This is ignored if `text` isn't a Unicode string. `errors` - str - How to handle Unicode encoding errors. Default is ``'strict'``. See ``help(unicode.encode)`` for the options. This is ignored if `text` isn't a Unicode string. `linesep` - keyword argument - str/unicode - The sequence of characters to be used to mark end-of-line. The default is :data:`os.linesep`. You can also specify ``None`` to leave all newlines as they are in `text`. `append` - keyword argument - bool - Specifies what to do if the file already exists (``True``: append to the end of it; ``False``: overwrite it.) The default is ``False``. --- Newline handling. ``write_text()`` converts all standard end-of-line sequences (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default end-of-line sequence (see :data:`os.linesep`; on Windows, for example, the end-of-line marker is ``'\r\n'``). If you don't like your platform's default, you can override it using the `linesep=` keyword argument. If you specifically want ``write_text()`` to preserve the newlines as-is, use ``linesep=None``. This applies to Unicode text the same as to 8-bit text, except there are three additional standard Unicode end-of-line sequences: ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``. (This is slightly different from when you open a file for writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')`` in Python.) --- Unicode If `text` isn't Unicode, then apart from newline handling, the bytes are written verbatim to the file. The `encoding` and `errors` arguments are not used and must be omitted. If `text` is Unicode, it is first converted to :func:`bytes` using the specified `encoding` (or the default encoding if `encoding` isn't specified). The `errors` argument applies only to this conversion. """ if isinstance(text, text_type): if linesep is not None: text = U_NEWLINE.sub(linesep, text) text = text.encode(encoding or sys.getdefaultencoding(), errors) else: assert encoding is None text = NEWLINE.sub(linesep, text) self.write_bytes(text, append=append) def lines(self, encoding=None, errors='strict', retain=True): r""" Open this file, read all lines, return them in a list. Optional arguments: `encoding` - The Unicode encoding (or character set) of the file. The default is ``None``, meaning the content of the file is read as 8-bit characters and returned as a list of (non-Unicode) str objects. `errors` - How to handle Unicode errors; see help(str.decode) for the options. Default is ``'strict'``. `retain` - If ``True``, retain newline characters; but all newline character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are translated to ``'\n'``. If ``False``, newline characters are stripped off. Default is ``True``. This uses ``'U'`` mode. .. seealso:: :meth:`text` """ if encoding is None and retain: with self.open('U') as f: return f.readlines() else: return self.text(encoding, errors).splitlines(retain) def write_lines(self, lines, encoding=None, errors='strict', linesep=os.linesep, append=False): r""" Write the given lines of text to this file. By default this overwrites any existing file at this path. This puts a platform-specific newline sequence on every line. See `linesep` below. `lines` - A list of strings. `encoding` - A Unicode encoding to use. This applies only if `lines` contains any Unicode strings. `errors` - How to handle errors in Unicode encoding. This also applies only to Unicode strings. linesep - The desired line-ending. This line-ending is applied to every line. If a line already has any standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``, ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will be stripped off and this will be used instead. The default is os.linesep, which is platform-dependent (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.). Specify ``None`` to write the lines as-is, like :meth:`file.writelines`. Use the keyword argument ``append=True`` to append lines to the file. The default is to overwrite the file. .. warning :: When you use this with Unicode data, if the encoding of the existing data in the file is different from the encoding you specify with the `encoding=` parameter, the result is mixed-encoding data, which can really confuse someone trying to read the file later. """ with self.open('ab' if append else 'wb') as f: for l in lines: isUnicode = isinstance(l, text_type) if linesep is not None: pattern = U_NL_END if isUnicode else NL_END l = pattern.sub('', l) + linesep if isUnicode: l = l.encode(encoding or sys.getdefaultencoding(), errors) f.write(l) def read_md5(self): """ Calculate the md5 hash for this file. This reads through the entire file. .. seealso:: :meth:`read_hash` """ return self.read_hash('md5') def _hash(self, hash_name): """ Returns a hash object for the file at the current path. `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``) that's available in the :mod:`hashlib` module. """ m = hashlib.new(hash_name) for chunk in self.chunks(8192, mode="rb"): m.update(chunk) return m def read_hash(self, hash_name): """ Calculate given hash for this file. List of supported hashes can be obtained from :mod:`hashlib` package. This reads the entire file. .. seealso:: :meth:`hashlib.hash.digest` """ return self._hash(hash_name).digest() def read_hexhash(self, hash_name): """ Calculate given hash for this file, returning hexdigest. List of supported hashes can be obtained from :mod:`hashlib` package. This reads the entire file. .. seealso:: :meth:`hashlib.hash.hexdigest` """ return self._hash(hash_name).hexdigest() # --- Methods for querying the filesystem. # N.B. On some platforms, the os.path functions may be implemented in C # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get # bound. Playing it safe and wrapping them all in method calls. def isabs(self): """ .. seealso:: :func:`os.path.isabs` """ return self.module.isabs(self) def exists(self): """ .. seealso:: :func:`os.path.exists` """ return self.module.exists(self) def isdir(self): """ .. seealso:: :func:`os.path.isdir` """ return self.module.isdir(self) def isfile(self): """ .. seealso:: :func:`os.path.isfile` """ return self.module.isfile(self) def islink(self): """ .. seealso:: :func:`os.path.islink` """ return self.module.islink(self) def ismount(self): """ .. seealso:: :func:`os.path.ismount` """ return self.module.ismount(self) def samefile(self, other): """ .. seealso:: :func:`os.path.samefile` """ if not hasattr(self.module, 'samefile'): other = Path(other).realpath().normpath().normcase() return self.realpath().normpath().normcase() == other return self.module.samefile(self, other) def getatime(self): """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """ return self.module.getatime(self) atime = property( getatime, None, None, """ Last access time of the file. .. seealso:: :meth:`getatime`, :func:`os.path.getatime` """) def getmtime(self): """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """ return self.module.getmtime(self) mtime = property( getmtime, None, None, """ Last-modified time of the file. .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime` """) def getctime(self): """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """ return self.module.getctime(self) ctime = property( getctime, None, None, """ Creation time of the file. .. seealso:: :meth:`getctime`, :func:`os.path.getctime` """) def getsize(self): """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """ return self.module.getsize(self) size = property( getsize, None, None, """ Size of the file, in bytes. .. seealso:: :meth:`getsize`, :func:`os.path.getsize` """) if hasattr(os, 'access'): def access(self, mode): """ Return ``True`` if current user has access to this path. mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`, :data:`os.W_OK`, :data:`os.X_OK` .. seealso:: :func:`os.access` """ return os.access(self, mode) def stat(self): """ Perform a ``stat()`` system call on this path. .. seealso:: :meth:`lstat`, :func:`os.stat` """ return os.stat(self) def lstat(self): """ Like :meth:`stat`, but do not follow symbolic links. .. seealso:: :meth:`stat`, :func:`os.lstat` """ return os.lstat(self) def __get_owner_windows(self): """ Return the name of the owner of this file or directory. Follow symbolic links. Return a name of the form ``r'DOMAIN\\User Name'``; may be a group. .. seealso:: :attr:`owner` """ desc = win32security.GetFileSecurity( self, win32security.OWNER_SECURITY_INFORMATION) sid = desc.GetSecurityDescriptorOwner() account, domain, typecode = win32security.LookupAccountSid(None, sid) return domain + '\\' + account def __get_owner_unix(self): """ Return the name of the owner of this file or directory. Follow symbolic links. .. seealso:: :attr:`owner` """ st = self.stat() return pwd.getpwuid(st.st_uid).pw_name def __get_owner_not_implemented(self): raise NotImplementedError("Ownership not available on this platform.") if 'win32security' in globals(): get_owner = __get_owner_windows elif 'pwd' in globals(): get_owner = __get_owner_unix else: get_owner = __get_owner_not_implemented owner = property( get_owner, None, None, """ Name of the owner of this file or directory. .. seealso:: :meth:`get_owner`""") if hasattr(os, 'statvfs'): def statvfs(self): """ Perform a ``statvfs()`` system call on this path. .. seealso:: :func:`os.statvfs` """ return os.statvfs(self) if hasattr(os, 'pathconf'): def pathconf(self, name): """ .. seealso:: :func:`os.pathconf` """ return os.pathconf(self, name) # # --- Modifying operations on files and directories def utime(self, times): """ Set the access and modified times of this file. .. seealso:: :func:`os.utime` """ os.utime(self, times) return self def chmod(self, mode): """ Set the mode. May be the new mode (os.chmod behavior) or a `symbolic mode `_. .. seealso:: :func:`os.chmod` """ if isinstance(mode, string_types): mask = _multi_permission_mask(mode) mode = mask(self.stat().st_mode) os.chmod(self, mode) return self def chown(self, uid=-1, gid=-1): """ Change the owner and group by names rather than the uid or gid numbers. .. seealso:: :func:`os.chown` """ if hasattr(os, 'chown'): if 'pwd' in globals() and isinstance(uid, string_types): uid = pwd.getpwnam(uid).pw_uid if 'grp' in globals() and isinstance(gid, string_types): gid = grp.getgrnam(gid).gr_gid os.chown(self, uid, gid) else: raise NotImplementedError("Ownership not available on this platform.") return self def rename(self, new): """ .. seealso:: :func:`os.rename` """ os.rename(self, new) return self._next_class(new) def renames(self, new): """ .. seealso:: :func:`os.renames` """ os.renames(self, new) return self._next_class(new) # # --- Create/delete operations on directories def mkdir(self, mode=0o777): """ .. seealso:: :func:`os.mkdir` """ os.mkdir(self, mode) return self def mkdir_p(self, mode=0o777): """ Like :meth:`mkdir`, but does not raise an exception if the directory already exists. """ try: self.mkdir(mode) except OSError: _, e, _ = sys.exc_info() if e.errno != errno.EEXIST: raise return self def makedirs(self, mode=0o777): """ .. seealso:: :func:`os.makedirs` """ os.makedirs(self, mode) return self def makedirs_p(self, mode=0o777): """ Like :meth:`makedirs`, but does not raise an exception if the directory already exists. """ try: self.makedirs(mode) except OSError: _, e, _ = sys.exc_info() if e.errno != errno.EEXIST: raise return self def rmdir(self): """ .. seealso:: :func:`os.rmdir` """ os.rmdir(self) return self def rmdir_p(self): """ Like :meth:`rmdir`, but does not raise an exception if the directory is not empty or does not exist. """ try: self.rmdir() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: raise return self def removedirs(self): """ .. seealso:: :func:`os.removedirs` """ os.removedirs(self) return self def removedirs_p(self): """ Like :meth:`removedirs`, but does not raise an exception if the directory is not empty or does not exist. """ try: self.removedirs() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: raise return self # --- Modifying operations on files def touch(self): """ Set the access/modified times of this file to the current time. Create the file if it does not exist. """ fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666) os.close(fd) os.utime(self, None) return self def remove(self): """ .. seealso:: :func:`os.remove` """ os.remove(self) return self def remove_p(self): """ Like :meth:`remove`, but does not raise an exception if the file does not exist. """ try: self.unlink() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOENT: raise return self def unlink(self): """ .. seealso:: :func:`os.unlink` """ os.unlink(self) return self def unlink_p(self): """ Like :meth:`unlink`, but does not raise an exception if the file does not exist. """ self.remove_p() return self # --- Links if hasattr(os, 'link'): def link(self, newpath): """ Create a hard link at `newpath`, pointing to this file. .. seealso:: :func:`os.link` """ os.link(self, newpath) return self._next_class(newpath) if hasattr(os, 'symlink'): def symlink(self, newlink): """ Create a symbolic link at `newlink`, pointing here. .. seealso:: :func:`os.symlink` """ os.symlink(self, newlink) return self._next_class(newlink) if hasattr(os, 'readlink'): def readlink(self): """ Return the path to which this symbolic link points. The result may be an absolute or a relative path. .. seealso:: :meth:`readlinkabs`, :func:`os.readlink` """ return self._next_class(os.readlink(self)) def readlinkabs(self): """ Return the path to which this symbolic link points. The result is always an absolute path. .. seealso:: :meth:`readlink`, :func:`os.readlink` """ p = self.readlink() if p.isabs(): return p else: return (self.parent / p).abspath() # High-level functions from shutil # These functions will be bound to the instance such that # Path(name).copy(target) will invoke shutil.copy(name, target) copyfile = shutil.copyfile copymode = shutil.copymode copystat = shutil.copystat copy = shutil.copy copy2 = shutil.copy2 copytree = shutil.copytree if hasattr(shutil, 'move'): move = shutil.move rmtree = shutil.rmtree def rmtree_p(self): """ Like :meth:`rmtree`, but does not raise an exception if the directory does not exist. """ try: self.rmtree() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOENT: raise return self def chdir(self): """ .. seealso:: :func:`os.chdir` """ os.chdir(self) cd = chdir def merge_tree(self, dst, symlinks=False, *args, **kwargs): """ Copy entire contents of self to dst, overwriting existing contents in dst with those in self. If the additional keyword `update` is True, each `src` will only be copied if `dst` does not exist, or `src` is newer than `dst`. Note that the technique employed stages the files in a temporary directory first, so this function is not suitable for merging trees with large files, especially if the temporary directory is not capable of storing a copy of the entire source tree. """ update = kwargs.pop('update', False) with tempdir() as _temp_dir: # first copy the tree to a stage directory to support # the parameters and behavior of copytree. stage = _temp_dir / str(hash(self)) self.copytree(stage, symlinks, *args, **kwargs) # now copy everything from the stage directory using # the semantics of dir_util.copy_tree dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks, update=update) # # --- Special stuff from os if hasattr(os, 'chroot'): def chroot(self): """ .. seealso:: :func:`os.chroot` """ os.chroot(self) if hasattr(os, 'startfile'): def startfile(self): """ .. seealso:: :func:`os.startfile` """ os.startfile(self) return self # in-place re-writing, courtesy of Martijn Pieters # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ @contextlib.contextmanager def in_place(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None, backup_extension=None): """ A context in which a file may be re-written in-place with new content. Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable` replaces `readable`. If an exception occurs, the old file is restored, removing the written data. Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are allowed. A :exc:`ValueError` is raised on invalid modes. For example, to add line numbers to a file:: p = Path(filename) assert p.isfile() with p.in_place() as (reader, writer): for number, line in enumerate(reader, 1): writer.write('{0:3}: '.format(number))) writer.write(line) Thereafter, the file at `filename` will have line numbers in it. """ import io if set(mode).intersection('wa+'): raise ValueError('Only read-only file modes can be used') # move existing file to backup, create new file with same permissions # borrowed extensively from the fileinput module backup_fn = self + (backup_extension or os.extsep + 'bak') try: os.unlink(backup_fn) except os.error: pass os.rename(self, backup_fn) readable = io.open(backup_fn, mode, buffering=buffering, encoding=encoding, errors=errors, newline=newline) try: perm = os.fstat(readable.fileno()).st_mode except OSError: writable = open(self, 'w' + mode.replace('r', ''), buffering=buffering, encoding=encoding, errors=errors, newline=newline) else: os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC if hasattr(os, 'O_BINARY'): os_mode |= os.O_BINARY fd = os.open(self, os_mode, perm) writable = io.open(fd, "w" + mode.replace('r', ''), buffering=buffering, encoding=encoding, errors=errors, newline=newline) try: if hasattr(os, 'chmod'): os.chmod(self, perm) except OSError: pass try: yield readable, writable except Exception: # move backup back readable.close() writable.close() try: os.unlink(self) except os.error: pass os.rename(backup_fn, self) raise else: readable.close() writable.close() finally: try: os.unlink(backup_fn) except os.error: pass @ClassProperty @classmethod def special(cls): """ Return a SpecialResolver object suitable referencing a suitable directory for the relevant platform for the given type of content. For example, to get a user config directory, invoke: dir = Path.special().user.config Uses the `appdirs `_ to resolve the paths in a platform-friendly way. To create a config directory for 'My App', consider: dir = Path.special("My App").user.config.makedirs_p() If the ``appdirs`` module is not installed, invocation of special will raise an ImportError. """ return functools.partial(SpecialResolver, cls) class SpecialResolver(object): class ResolverScope: def __init__(self, paths, scope): self.paths = paths self.scope = scope def __getattr__(self, class_): return self.paths.get_dir(self.scope, class_) def __init__(self, path_class, *args, **kwargs): appdirs = importlib.import_module('appdirs') # let appname default to None until # https://github.com/ActiveState/appdirs/issues/55 is solved. not args and kwargs.setdefault('appname', None) vars(self).update( path_class=path_class, wrapper=appdirs.AppDirs(*args, **kwargs), ) def __getattr__(self, scope): return self.ResolverScope(self, scope) def get_dir(self, scope, class_): """ Return the callable function from appdirs, but with the result wrapped in self.path_class """ prop_name = '{scope}_{class_}_dir'.format(**locals()) value = getattr(self.wrapper, prop_name) MultiPath = Multi.for_class(self.path_class) return MultiPath.detect(value) class Multi: """ A mix-in for a Path which may contain multiple Path separated by pathsep. """ @classmethod def for_class(cls, path_cls): name = 'Multi' + path_cls.__name__ if PY2: name = str(name) return type(name, (cls, path_cls), {}) @classmethod def detect(cls, input): if os.pathsep not in input: cls = cls._next_class return cls(input) def __iter__(self): return iter(map(self._next_class, self.split(os.pathsep))) @ClassProperty @classmethod def _next_class(cls): """ Multi-subclasses should use the parent class """ return next( class_ for class_ in cls.__mro__ if not issubclass(class_, Multi) ) class tempdir(Path): """ A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the same parameters that you can use as a context manager. Example: with tempdir() as d: # do stuff with the Path object "d" # here the directory is deleted automatically .. seealso:: :func:`tempfile.mkdtemp` """ @ClassProperty @classmethod def _next_class(cls): return Path def __new__(cls, *args, **kwargs): dirname = tempfile.mkdtemp(*args, **kwargs) return super(tempdir, cls).__new__(cls, dirname) def __init__(self, *args, **kwargs): pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if not exc_value: self.rmtree() def _multi_permission_mask(mode): """ Support multiple, comma-separated Unix chmod symbolic modes. >>> _multi_permission_mask('a=r,u+w')(0) == 0o644 True """ compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs)) return functools.reduce(compose, map(_permission_mask, mode.split(','))) def _permission_mask(mode): """ Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function suitable for applying to a mask to affect that change. >>> mask = _permission_mask('ugo+rwx') >>> mask(0o554) == 0o777 True >>> _permission_mask('go-x')(0o777) == 0o766 True >>> _permission_mask('o-x')(0o445) == 0o444 True >>> _permission_mask('a+x')(0) == 0o111 True >>> _permission_mask('a=rw')(0o057) == 0o666 True >>> _permission_mask('u=x')(0o666) == 0o166 True >>> _permission_mask('g=')(0o157) == 0o107 True """ # parse the symbolic mode parsed = re.match('(?P[ugoa]+)(?P[-+=])(?P[rwx]*)$', mode) if not parsed: raise ValueError("Unrecognized symbolic mode", mode) # generate a mask representing the specified permission spec_map = dict(r=4, w=2, x=1) specs = (spec_map[perm] for perm in parsed.group('what')) spec = functools.reduce(operator.or_, specs, 0) # now apply spec to each subject in who shift_map = dict(u=6, g=3, o=0) who = parsed.group('who').replace('a', 'ugo') masks = (spec << shift_map[subj] for subj in who) mask = functools.reduce(operator.or_, masks) op = parsed.group('op') # if op is -, invert the mask if op == '-': mask ^= 0o777 # if op is =, retain extant values for unreferenced subjects if op == '=': masks = (0o7 << shift_map[subj] for subj in who) retain = functools.reduce(operator.or_, masks) ^ 0o777 op_map = { '+': operator.or_, '-': operator.and_, '=': lambda mask, target: target & retain ^ mask, } return functools.partial(op_map[op], mask) class CaseInsensitivePattern(text_type): """ A string with a ``'normcase'`` property, suitable for passing to :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`, :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive. For example, to get all files ending in .py, .Py, .pY, or .PY in the current directory:: from path import Path, CaseInsensitivePattern as ci Path('.').files(ci('*.py')) """ @property def normcase(self): return __import__('ntpath').normcase ######################## # Backward-compatibility class path(Path): def __new__(cls, *args, **kwargs): msg = "path is deprecated. Use Path instead." warnings.warn(msg, DeprecationWarning) return Path.__new__(cls, *args, **kwargs) __all__ += ['path'] ######################## parse_type-0.5.6/tasks/_vendor/pathlib.py000066400000000000000000001210111372661661400204630ustar00rootroot00000000000000import fnmatch import functools import io import ntpath import os import posixpath import re import sys import time from collections import Sequence from contextlib import contextmanager from errno import EINVAL, ENOENT from operator import attrgetter from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO try: from urllib import quote as urlquote, quote as urlquote_from_bytes except ImportError: from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes try: intern = intern except NameError: intern = sys.intern try: basestring = basestring except NameError: basestring = str supports_symlinks = True try: import nt except ImportError: nt = None else: if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2): from nt import _getfinalpathname else: supports_symlinks = False _getfinalpathname = None __all__ = [ "PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath", ] # # Internals # _py2 = sys.version_info < (3,) _py2_fs_encoding = 'ascii' def _py2_fsencode(parts): # py2 => minimal unicode support return [part.encode(_py2_fs_encoding) if isinstance(part, unicode) else part for part in parts] def _is_wildcard_pattern(pat): # Whether this pattern needs actual matching using fnmatch, or can # be looked up directly as a file. return "*" in pat or "?" in pat or "[" in pat class _Flavour(object): """A flavour implements a particular (platform-specific) set of path semantics.""" def __init__(self): self.join = self.sep.join def parse_parts(self, parts): if _py2: parts = _py2_fsencode(parts) parsed = [] sep = self.sep altsep = self.altsep drv = root = '' it = reversed(parts) for part in it: if not part: continue if altsep: part = part.replace(altsep, sep) drv, root, rel = self.splitroot(part) if sep in rel: for x in reversed(rel.split(sep)): if x and x != '.': parsed.append(intern(x)) else: if rel and rel != '.': parsed.append(intern(rel)) if drv or root: if not drv: # If no drive is present, try to find one in the previous # parts. This makes the result of parsing e.g. # ("C:", "/", "a") reasonably intuitive. for part in it: drv = self.splitroot(part)[0] if drv: break break if drv or root: parsed.append(drv + root) parsed.reverse() return drv, root, parsed def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2): """ Join the two paths represented by the respective (drive, root, parts) tuples. Return a new (drive, root, parts) tuple. """ if root2: if not drv2 and drv: return drv, root2, [drv + root2] + parts2[1:] elif drv2: if drv2 == drv or self.casefold(drv2) == self.casefold(drv): # Same drive => second path is relative to the first return drv, root, parts + parts2[1:] else: # Second path is non-anchored (common case) return drv, root, parts + parts2 return drv2, root2, parts2 class _WindowsFlavour(_Flavour): # Reference for Windows paths can be found at # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx sep = '\\' altsep = '/' has_drv = True pathmod = ntpath is_supported = (nt is not None) drive_letters = ( set(chr(x) for x in range(ord('a'), ord('z') + 1)) | set(chr(x) for x in range(ord('A'), ord('Z') + 1)) ) ext_namespace_prefix = '\\\\?\\' reserved_names = ( set(['CON', 'PRN', 'AUX', 'NUL']) | set(['COM%d' % i for i in range(1, 10)]) | set(['LPT%d' % i for i in range(1, 10)]) ) # Interesting findings about extended paths: # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported # but '\\?\c:/a' is not # - extended paths are always absolute; "relative" extended paths will # fail. def splitroot(self, part, sep=sep): first = part[0:1] second = part[1:2] if (second == sep and first == sep): # XXX extended paths should also disable the collapsing of "." # components (according to MSDN docs). prefix, part = self._split_extended_path(part) first = part[0:1] second = part[1:2] else: prefix = '' third = part[2:3] if (second == sep and first == sep and third != sep): # is a UNC path: # vvvvvvvvvvvvvvvvvvvvv root # \\machine\mountpoint\directory\etc\... # directory ^^^^^^^^^^^^^^ index = part.find(sep, 2) if index != -1: index2 = part.find(sep, index + 1) # a UNC path can't have two slashes in a row # (after the initial two) if index2 != index + 1: if index2 == -1: index2 = len(part) if prefix: return prefix + part[1:index2], sep, part[index2+1:] else: return part[:index2], sep, part[index2+1:] drv = root = '' if second == ':' and first in self.drive_letters: drv = part[:2] part = part[2:] first = third if first == sep: root = first part = part.lstrip(sep) return prefix + drv, root, part def casefold(self, s): return s.lower() def casefold_parts(self, parts): return [p.lower() for p in parts] def resolve(self, path): s = str(path) if not s: return os.getcwd() if _getfinalpathname is not None: return self._ext_to_normal(_getfinalpathname(s)) # Means fallback on absolute return None def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): prefix = '' if s.startswith(ext_prefix): prefix = s[:4] s = s[4:] if s.startswith('UNC\\'): prefix += s[:3] s = '\\' + s[3:] return prefix, s def _ext_to_normal(self, s): # Turn back an extended path into a normal DOS-like path return self._split_extended_path(s)[1] def is_reserved(self, parts): # NOTE: the rules for reserved names seem somewhat complicated # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). # We err on the side of caution and return True for paths which are # not considered reserved by Windows. if not parts: return False if parts[0].startswith('\\\\'): # UNC paths are never reserved return False return parts[-1].partition('.')[0].upper() in self.reserved_names def make_uri(self, path): # Under Windows, file URIs use the UTF-8 encoding. drive = path.drive if len(drive) == 2 and drive[1] == ':': # It's a path on a local drive => 'file:///c:/a/b' rest = path.as_posix()[2:].lstrip('/') return 'file:///%s/%s' % ( drive, urlquote_from_bytes(rest.encode('utf-8'))) else: # It's a path on a network drive => 'file://host/share/a/b' return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8')) class _PosixFlavour(_Flavour): sep = '/' altsep = '' has_drv = False pathmod = posixpath is_supported = (os.name != 'nt') def splitroot(self, part, sep=sep): if part and part[0] == sep: stripped_part = part.lstrip(sep) # According to POSIX path resolution: # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 # "A pathname that begins with two successive slashes may be # interpreted in an implementation-defined manner, although more # than two leading slashes shall be treated as a single slash". if len(part) - len(stripped_part) == 2: return '', sep * 2, stripped_part else: return '', sep, stripped_part else: return '', '', part def casefold(self, s): return s def casefold_parts(self, parts): return parts def resolve(self, path): sep = self.sep accessor = path._accessor seen = {} def _resolve(path, rest): if rest.startswith(sep): path = '' for name in rest.split(sep): if not name or name == '.': # current dir continue if name == '..': # parent dir path, _, _ = path.rpartition(sep) continue newpath = path + sep + name if newpath in seen: # Already seen this path path = seen[newpath] if path is not None: # use cached value continue # The symlink is not resolved, so we must have a symlink loop. raise RuntimeError("Symlink loop from %r" % newpath) # Resolve the symbolic link try: target = accessor.readlink(newpath) except OSError as e: if e.errno != EINVAL: raise # Not a symlink path = newpath else: seen[newpath] = None # not resolved symlink path = _resolve(path, target) seen[newpath] = path # resolved symlink return path # NOTE: according to POSIX, getcwd() cannot contain path components # which are symlinks. base = '' if path.is_absolute() else os.getcwd() return _resolve(base, str(path)) or sep def is_reserved(self, parts): return False def make_uri(self, path): # We represent the path using the local filesystem encoding, # for portability to other applications. bpath = bytes(path) return 'file://' + urlquote_from_bytes(bpath) _windows_flavour = _WindowsFlavour() _posix_flavour = _PosixFlavour() class _Accessor: """An accessor implements a particular (system-specific or not) way of accessing paths on the filesystem.""" class _NormalAccessor(_Accessor): def _wrap_strfunc(strfunc): @functools.wraps(strfunc) def wrapped(pathobj, *args): return strfunc(str(pathobj), *args) return staticmethod(wrapped) def _wrap_binary_strfunc(strfunc): @functools.wraps(strfunc) def wrapped(pathobjA, pathobjB, *args): return strfunc(str(pathobjA), str(pathobjB), *args) return staticmethod(wrapped) stat = _wrap_strfunc(os.stat) lstat = _wrap_strfunc(os.lstat) open = _wrap_strfunc(os.open) listdir = _wrap_strfunc(os.listdir) chmod = _wrap_strfunc(os.chmod) if hasattr(os, "lchmod"): lchmod = _wrap_strfunc(os.lchmod) else: def lchmod(self, pathobj, mode): raise NotImplementedError("lchmod() not available on this system") mkdir = _wrap_strfunc(os.mkdir) unlink = _wrap_strfunc(os.unlink) rmdir = _wrap_strfunc(os.rmdir) rename = _wrap_binary_strfunc(os.rename) if sys.version_info >= (3, 3): replace = _wrap_binary_strfunc(os.replace) if nt: if supports_symlinks: symlink = _wrap_binary_strfunc(os.symlink) else: def symlink(a, b, target_is_directory): raise NotImplementedError("symlink() not available on this system") else: # Under POSIX, os.symlink() takes two args @staticmethod def symlink(a, b, target_is_directory): return os.symlink(str(a), str(b)) utime = _wrap_strfunc(os.utime) # Helper for resolve() def readlink(self, path): return os.readlink(path) _normal_accessor = _NormalAccessor() # # Globbing helpers # @contextmanager def _cached(func): try: func.__cached__ yield func except AttributeError: cache = {} def wrapper(*args): try: return cache[args] except KeyError: value = cache[args] = func(*args) return value wrapper.__cached__ = True try: yield wrapper finally: cache.clear() def _make_selector(pattern_parts): pat = pattern_parts[0] child_parts = pattern_parts[1:] if pat == '**': cls = _RecursiveWildcardSelector elif '**' in pat: raise ValueError("Invalid pattern: '**' can only be an entire path component") elif _is_wildcard_pattern(pat): cls = _WildcardSelector else: cls = _PreciseSelector return cls(pat, child_parts) if hasattr(functools, "lru_cache"): _make_selector = functools.lru_cache()(_make_selector) class _Selector: """A selector matches a specific glob pattern part against the children of a given path.""" def __init__(self, child_parts): self.child_parts = child_parts if child_parts: self.successor = _make_selector(child_parts) else: self.successor = _TerminatingSelector() def select_from(self, parent_path): """Iterate over all child paths of `parent_path` matched by this selector. This can contain parent_path itself.""" path_cls = type(parent_path) is_dir = path_cls.is_dir exists = path_cls.exists listdir = parent_path._accessor.listdir return self._select_from(parent_path, is_dir, exists, listdir) class _TerminatingSelector: def _select_from(self, parent_path, is_dir, exists, listdir): yield parent_path class _PreciseSelector(_Selector): def __init__(self, name, child_parts): self.name = name _Selector.__init__(self, child_parts) def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return path = parent_path._make_child_relpath(self.name) if exists(path): for p in self.successor._select_from(path, is_dir, exists, listdir): yield p class _WildcardSelector(_Selector): def __init__(self, pat, child_parts): self.pat = re.compile(fnmatch.translate(pat)) _Selector.__init__(self, child_parts) def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return cf = parent_path._flavour.casefold for name in listdir(parent_path): casefolded = cf(name) if self.pat.match(casefolded): path = parent_path._make_child_relpath(name) for p in self.successor._select_from(path, is_dir, exists, listdir): yield p class _RecursiveWildcardSelector(_Selector): def __init__(self, pat, child_parts): _Selector.__init__(self, child_parts) def _iterate_directories(self, parent_path, is_dir, listdir): yield parent_path for name in listdir(parent_path): path = parent_path._make_child_relpath(name) if is_dir(path): for p in self._iterate_directories(path, is_dir, listdir): yield p def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return with _cached(listdir) as listdir: yielded = set() try: successor_select = self.successor._select_from for starting_point in self._iterate_directories(parent_path, is_dir, listdir): for p in successor_select(starting_point, is_dir, exists, listdir): if p not in yielded: yield p yielded.add(p) finally: yielded.clear() # # Public API # class _PathParents(Sequence): """This object provides sequence-like access to the logical ancestors of a path. Don't try to construct it yourself.""" __slots__ = ('_pathcls', '_drv', '_root', '_parts') def __init__(self, path): # We don't store the instance to avoid reference cycles self._pathcls = type(path) self._drv = path._drv self._root = path._root self._parts = path._parts def __len__(self): if self._drv or self._root: return len(self._parts) - 1 else: return len(self._parts) def __getitem__(self, idx): if idx < 0 or idx >= len(self): raise IndexError(idx) return self._pathcls._from_parsed_parts(self._drv, self._root, self._parts[:-idx - 1]) def __repr__(self): return "<{0}.parents>".format(self._pathcls.__name__) class PurePath(object): """PurePath represents a filesystem path and offers operations which don't imply any actual filesystem I/O. Depending on your system, instantiating a PurePath will return either a PurePosixPath or a PureWindowsPath object. You can also instantiate either of these classes directly, regardless of your system. """ __slots__ = ( '_drv', '_root', '_parts', '_str', '_hash', '_pparts', '_cached_cparts', ) def __new__(cls, *args): """Construct a PurePath from one or several strings and or existing PurePath objects. The strings and path objects are combined so as to yield a canonicalized path, which is incorporated into the new PurePath object. """ if cls is PurePath: cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return cls._from_parts(args) def __reduce__(self): # Using the parts tuple helps share interned path parts # when pickling related paths. return (self.__class__, tuple(self._parts)) @classmethod def _parse_args(cls, args): # This is useful when you don't want to create an instance, just # canonicalize some constructor arguments. parts = [] for a in args: if isinstance(a, PurePath): parts += a._parts elif isinstance(a, basestring): parts.append(a) else: raise TypeError( "argument should be a path or str object, not %r" % type(a)) return cls._flavour.parse_parts(parts) @classmethod def _from_parts(cls, args, init=True): # We need to call _parse_args on the instance, so as to get the # right flavour. self = object.__new__(cls) drv, root, parts = self._parse_args(args) self._drv = drv self._root = root self._parts = parts if init: self._init() return self @classmethod def _from_parsed_parts(cls, drv, root, parts, init=True): self = object.__new__(cls) self._drv = drv self._root = root self._parts = parts if init: self._init() return self @classmethod def _format_parsed_parts(cls, drv, root, parts): if drv or root: return drv + root + cls._flavour.join(parts[1:]) else: return cls._flavour.join(parts) def _init(self): # Overriden in concrete Path pass def _make_child(self, args): drv, root, parts = self._parse_args(args) drv, root, parts = self._flavour.join_parsed_parts( self._drv, self._root, self._parts, drv, root, parts) return self._from_parsed_parts(drv, root, parts) def __str__(self): """Return the string representation of the path, suitable for passing to system calls.""" try: return self._str except AttributeError: self._str = self._format_parsed_parts(self._drv, self._root, self._parts) or '.' return self._str def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" f = self._flavour return str(self).replace(f.sep, '/') def __bytes__(self): """Return the bytes representation of the path. This is only recommended to use under Unix.""" if sys.version_info < (3, 2): raise NotImplementedError("needs Python 3.2 or later") return os.fsencode(str(self)) def __repr__(self): return "{0}({1!r})".format(self.__class__.__name__, self.as_posix()) def as_uri(self): """Return the path as a 'file' URI.""" if not self.is_absolute(): raise ValueError("relative path can't be expressed as a file URI") return self._flavour.make_uri(self) @property def _cparts(self): # Cached casefolded parts, for hashing and comparison try: return self._cached_cparts except AttributeError: self._cached_cparts = self._flavour.casefold_parts(self._parts) return self._cached_cparts def __eq__(self, other): if not isinstance(other, PurePath): return NotImplemented return self._cparts == other._cparts and self._flavour is other._flavour def __ne__(self, other): return not self == other def __hash__(self): try: return self._hash except AttributeError: self._hash = hash(tuple(self._cparts)) return self._hash def __lt__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts < other._cparts def __le__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts <= other._cparts def __gt__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts > other._cparts def __ge__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts >= other._cparts drive = property(attrgetter('_drv'), doc="""The drive prefix (letter or UNC path), if any.""") root = property(attrgetter('_root'), doc="""The root of the path, if any.""") @property def anchor(self): """The concatenation of the drive and root, or ''.""" anchor = self._drv + self._root return anchor @property def name(self): """The final path component, if any.""" parts = self._parts if len(parts) == (1 if (self._drv or self._root) else 0): return '' return parts[-1] @property def suffix(self): """The final component's last suffix, if any.""" name = self.name i = name.rfind('.') if 0 < i < len(name) - 1: return name[i:] else: return '' @property def suffixes(self): """A list of the final component's suffixes, if any.""" name = self.name if name.endswith('.'): return [] name = name.lstrip('.') return ['.' + suffix for suffix in name.split('.')[1:]] @property def stem(self): """The final path component, minus its last suffix.""" name = self.name i = name.rfind('.') if 0 < i < len(name) - 1: return name[:i] else: return name def with_name(self, name): """Return a new path with the file name changed.""" if not self.name: raise ValueError("%r has an empty name" % (self,)) return self._from_parsed_parts(self._drv, self._root, self._parts[:-1] + [name]) def with_suffix(self, suffix): """Return a new path with the file suffix changed (or added, if none).""" # XXX if suffix is None, should the current suffix be removed? drv, root, parts = self._flavour.parse_parts((suffix,)) if drv or root or len(parts) != 1: raise ValueError("Invalid suffix %r" % (suffix)) suffix = parts[0] if not suffix.startswith('.'): raise ValueError("Invalid suffix %r" % (suffix)) name = self.name if not name: raise ValueError("%r has an empty name" % (self,)) old_suffix = self.suffix if not old_suffix: name = name + suffix else: name = name[:-len(old_suffix)] + suffix return self._from_parsed_parts(self._drv, self._root, self._parts[:-1] + [name]) def relative_to(self, *other): """Return the relative path to another path identified by the passed arguments. If the operation is not possible (because this is not a subpath of the other path), raise ValueError. """ # For the purpose of this method, drive and root are considered # separate parts, i.e.: # Path('c:/').relative_to('c:') gives Path('/') # Path('c:/').relative_to('/') raise ValueError if not other: raise TypeError("need at least one argument") parts = self._parts drv = self._drv root = self._root if root: abs_parts = [drv, root] + parts[1:] else: abs_parts = parts to_drv, to_root, to_parts = self._parse_args(other) if to_root: to_abs_parts = [to_drv, to_root] + to_parts[1:] else: to_abs_parts = to_parts n = len(to_abs_parts) cf = self._flavour.casefold_parts if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts): formatted = self._format_parsed_parts(to_drv, to_root, to_parts) raise ValueError("{!r} does not start with {!r}" .format(str(self), str(formatted))) return self._from_parsed_parts('', root if n == 1 else '', abs_parts[n:]) @property def parts(self): """An object providing sequence-like access to the components in the filesystem path.""" # We cache the tuple to avoid building a new one each time .parts # is accessed. XXX is this necessary? try: return self._pparts except AttributeError: self._pparts = tuple(self._parts) return self._pparts def joinpath(self, *args): """Combine this path with one or several arguments, and return a new path representing either a subpath (if all arguments are relative paths) or a totally different path (if one of the arguments is anchored). """ return self._make_child(args) def __truediv__(self, key): return self._make_child((key,)) def __rtruediv__(self, key): return self._from_parts([key] + self._parts) if sys.version_info < (3,): __div__ = __truediv__ __rdiv__ = __rtruediv__ @property def parent(self): """The logical parent of the path.""" drv = self._drv root = self._root parts = self._parts if len(parts) == 1 and (drv or root): return self return self._from_parsed_parts(drv, root, parts[:-1]) @property def parents(self): """A sequence of this path's logical parents.""" return _PathParents(self) def is_absolute(self): """True if the path is absolute (has both a root and, if applicable, a drive).""" if not self._root: return False return not self._flavour.has_drv or bool(self._drv) def is_reserved(self): """Return True if the path contains one of the special names reserved by the system, if any.""" return self._flavour.is_reserved(self._parts) def match(self, path_pattern): """ Return True if this path matches the given pattern. """ cf = self._flavour.casefold path_pattern = cf(path_pattern) drv, root, pat_parts = self._flavour.parse_parts((path_pattern,)) if not pat_parts: raise ValueError("empty pattern") if drv and drv != cf(self._drv): return False if root and root != cf(self._root): return False parts = self._cparts if drv or root: if len(pat_parts) != len(parts): return False pat_parts = pat_parts[1:] elif len(pat_parts) > len(parts): return False for part, pat in zip(reversed(parts), reversed(pat_parts)): if not fnmatch.fnmatchcase(part, pat): return False return True class PurePosixPath(PurePath): _flavour = _posix_flavour __slots__ = () class PureWindowsPath(PurePath): _flavour = _windows_flavour __slots__ = () # Filesystem-accessing classes class Path(PurePath): __slots__ = ( '_accessor', ) def __new__(cls, *args, **kwargs): if cls is Path: cls = WindowsPath if os.name == 'nt' else PosixPath self = cls._from_parts(args, init=False) if not self._flavour.is_supported: raise NotImplementedError("cannot instantiate %r on your system" % (cls.__name__,)) self._init() return self def _init(self, # Private non-constructor arguments template=None, ): if template is not None: self._accessor = template._accessor else: self._accessor = _normal_accessor def _make_child_relpath(self, part): # This is an optimization used for dir walking. `part` must be # a single part relative to this path. parts = self._parts + [part] return self._from_parsed_parts(self._drv, self._root, parts) def _opener(self, name, flags, mode=0o666): # A stub for the opener argument to built-in open() return self._accessor.open(self, flags, mode) def _raw_open(self, flags, mode=0o777): """ Open the file pointed by this path and return a file descriptor, as os.open() does. """ return self._accessor.open(self, flags, mode) # Public API @classmethod def cwd(cls): """Return a new path pointing to the current working directory (as returned by os.getcwd()). """ return cls(os.getcwd()) def iterdir(self): """Iterate over the files in this directory. Does not yield any result for the special paths '.' and '..'. """ for name in self._accessor.listdir(self): if name in ('.', '..'): # Yielding a path object for these makes little sense continue yield self._make_child_relpath(name) def glob(self, pattern): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given pattern. """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) if drv or root: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(tuple(pattern_parts)) for p in selector.select_from(self): yield p def rglob(self, pattern): """Recursively yield all existing files (of any kind, including directories) matching the given pattern, anywhere in this subtree. """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) if drv or root: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(("**",) + tuple(pattern_parts)) for p in selector.select_from(self): yield p def absolute(self): """Return an absolute version of this path. This function works even if the path doesn't point to anything. No normalization is done, i.e. all '.' and '..' will be kept along. Use resolve() to get the canonical path to a file. """ # XXX untested yet! if self.is_absolute(): return self # FIXME this must defer to the specific flavour (and, under Windows, # use nt._getfullpathname()) obj = self._from_parts([os.getcwd()] + self._parts, init=False) obj._init(template=self) return obj def resolve(self): """ Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under Windows). """ s = self._flavour.resolve(self) if s is None: # No symlink resolution => for consistency, raise an error if # the path doesn't exist or is forbidden self.stat() s = str(self.absolute()) # Now we have no symlinks in the path, it's safe to normalize it. normed = self._flavour.pathmod.normpath(s) obj = self._from_parts((normed,), init=False) obj._init(template=self) return obj def stat(self): """ Return the result of the stat() system call on this path, like os.stat() does. """ return self._accessor.stat(self) def owner(self): """ Return the login name of the file owner. """ import pwd return pwd.getpwuid(self.stat().st_uid).pw_name def group(self): """ Return the group name of the file gid. """ import grp return grp.getgrgid(self.stat().st_gid).gr_name def open(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None): """ Open the file pointed by this path and return a file object, as the built-in open() function does. """ if sys.version_info >= (3, 3): return io.open(str(self), mode, buffering, encoding, errors, newline, opener=self._opener) else: return io.open(str(self), mode, buffering, encoding, errors, newline) def touch(self, mode=0o666, exist_ok=True): """ Create this file with the given access mode, if it doesn't exist. """ if exist_ok: # First try to bump modification time # Implementation note: GNU touch uses the UTIME_NOW option of # the utimensat() / futimens() functions. t = time.time() try: self._accessor.utime(self, (t, t)) except OSError: # Avoid exception chaining pass else: return flags = os.O_CREAT | os.O_WRONLY if not exist_ok: flags |= os.O_EXCL fd = self._raw_open(flags, mode) os.close(fd) def mkdir(self, mode=0o777, parents=False): if not parents: self._accessor.mkdir(self, mode) else: try: self._accessor.mkdir(self, mode) except OSError as e: if e.errno != ENOENT: raise self.parent.mkdir(parents=True) self._accessor.mkdir(self, mode) def chmod(self, mode): """ Change the permissions of the path, like os.chmod(). """ self._accessor.chmod(self, mode) def lchmod(self, mode): """ Like chmod(), except if the path points to a symlink, the symlink's permissions are changed, rather than its target's. """ self._accessor.lchmod(self, mode) def unlink(self): """ Remove this file or link. If the path is a directory, use rmdir() instead. """ self._accessor.unlink(self) def rmdir(self): """ Remove this directory. The directory must be empty. """ self._accessor.rmdir(self) def lstat(self): """ Like stat(), except if the path points to a symlink, the symlink's status information is returned, rather than its target's. """ return self._accessor.lstat(self) def rename(self, target): """ Rename this path to the given path. """ self._accessor.rename(self, target) def replace(self, target): """ Rename this path to the given path, clobbering the existing destination if it exists. """ if sys.version_info < (3, 3): raise NotImplementedError("replace() is only available " "with Python 3.3 and later") self._accessor.replace(self, target) def symlink_to(self, target, target_is_directory=False): """ Make this path a symlink pointing to the given path. Note the order of arguments (self, target) is the reverse of os.symlink's. """ self._accessor.symlink(target, self, target_is_directory) # Convenience functions for querying the stat results def exists(self): """ Whether this path exists. """ try: self.stat() except OSError as e: if e.errno != ENOENT: raise return False return True def is_dir(self): """ Whether this path is a directory. """ try: return S_ISDIR(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_file(self): """ Whether this path is a regular file (also True for symlinks pointing to regular files). """ try: return S_ISREG(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_symlink(self): """ Whether this path is a symbolic link. """ try: return S_ISLNK(self.lstat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist return False def is_block_device(self): """ Whether this path is a block device. """ try: return S_ISBLK(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_char_device(self): """ Whether this path is a character device. """ try: return S_ISCHR(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_fifo(self): """ Whether this path is a FIFO. """ try: return S_ISFIFO(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_socket(self): """ Whether this path is a socket. """ try: return S_ISSOCK(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False class PosixPath(Path, PurePosixPath): __slots__ = () class WindowsPath(Path, PureWindowsPath): __slots__ = () parse_type-0.5.6/tasks/_vendor/six.py000066400000000000000000000726221372661661400176600ustar00rootroot00000000000000"""Utilities for writing code that runs on Python 2 and 3""" # Copyright (c) 2010-2015 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import absolute_import import functools import itertools import operator import sys import types __author__ = "Benjamin Peterson " __version__ = "1.10.0" # Useful for very coarse version differentiation. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) if PY3: string_types = str, integer_types = int, class_types = type, text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str if sys.platform.startswith("java"): # Jython always uses 32 bits. MAXSIZE = int((1 << 31) - 1) else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): def __len__(self): return 1 << 31 try: len(X()) except OverflowError: # 32-bit MAXSIZE = int((1 << 31) - 1) else: # 64-bit MAXSIZE = int((1 << 63) - 1) del X def _add_doc(func, doc): """Add documentation to a function.""" func.__doc__ = doc def _import_module(name): """Import module, returning the module after the last dot.""" __import__(name) return sys.modules[name] class _LazyDescr(object): def __init__(self, name): self.name = name def __get__(self, obj, tp): result = self._resolve() setattr(obj, self.name, result) # Invokes __set__. try: # This is a bit ugly, but it avoids running this again by # removing this descriptor. delattr(obj.__class__, self.name) except AttributeError: pass return result class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: if new is None: new = name self.mod = new else: self.mod = old def _resolve(self): return _import_module(self.mod) def __getattr__(self, attr): _module = self._resolve() value = getattr(_module, attr) setattr(self, attr, value) return value class _LazyModule(types.ModuleType): def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): attrs = ["__doc__", "__name__"] attrs += [attr.name for attr in self._moved_attributes] return attrs # Subclasses should override this _moved_attributes = [] class MovedAttribute(_LazyDescr): def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: if new_mod is None: new_mod = name self.mod = new_mod if new_attr is None: if old_attr is None: new_attr = name else: new_attr = old_attr self.attr = new_attr else: self.mod = old_mod if old_attr is None: old_attr = name self.attr = old_attr def _resolve(self): module = _import_module(self.mod) return getattr(module, self.attr) class _SixMetaPathImporter(object): """ A meta path importer to import six.moves and its submodules. This class implements a PEP302 finder and loader. It should be compatible with Python 2.5 and all existing versions of Python3 """ def __init__(self, six_module_name): self.name = six_module_name self.known_modules = {} def _add_module(self, mod, *fullnames): for fullname in fullnames: self.known_modules[self.name + "." + fullname] = mod def _get_module(self, fullname): return self.known_modules[self.name + "." + fullname] def find_module(self, fullname, path=None): if fullname in self.known_modules: return self return None def __get_module(self, fullname): try: return self.known_modules[fullname] except KeyError: raise ImportError("This loader does not know module " + fullname) def load_module(self, fullname): try: # in case of a reload return sys.modules[fullname] except KeyError: pass mod = self.__get_module(fullname) if isinstance(mod, MovedModule): mod = mod._resolve() else: mod.__loader__ = self sys.modules[fullname] = mod return mod def is_package(self, fullname): """ Return true, if the named module is a package. We need this method to get correct spec objects with Python 3.4 (see PEP451) """ return hasattr(self.__get_module(fullname), "__path__") def get_code(self, fullname): """Return None Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None get_source = get_code # same as get_code _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" __path__ = [] # mark as package _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), MovedAttribute("UserDict", "UserDict", "collections"), MovedAttribute("UserList", "UserList", "collections"), MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), MovedModule("cPickle", "cPickle", "pickle"), MovedModule("queue", "Queue"), MovedModule("reprlib", "repr"), MovedModule("socketserver", "SocketServer"), MovedModule("_thread", "thread", "_thread"), MovedModule("tkinter", "Tkinter"), MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), MovedModule("tkinter_tix", "Tix", "tkinter.tix"), MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), ] # Add windows specific modules. if sys.platform == "win32": _moved_attributes += [ MovedModule("winreg", "_winreg"), ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) if isinstance(attr, MovedModule): _importer._add_module(attr, "moves." + attr.name) del attr _MovedItems._moved_attributes = _moved_attributes moves = _MovedItems(__name__ + ".moves") _importer._add_module(moves, "moves") class Module_six_moves_urllib_parse(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_parse""" _urllib_parse_moved_attributes = [ MovedAttribute("ParseResult", "urlparse", "urllib.parse"), MovedAttribute("SplitResult", "urlparse", "urllib.parse"), MovedAttribute("parse_qs", "urlparse", "urllib.parse"), MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), MovedAttribute("urldefrag", "urlparse", "urllib.parse"), MovedAttribute("urljoin", "urlparse", "urllib.parse"), MovedAttribute("urlparse", "urlparse", "urllib.parse"), MovedAttribute("urlsplit", "urlparse", "urllib.parse"), MovedAttribute("urlunparse", "urlparse", "urllib.parse"), MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), MovedAttribute("quote", "urllib", "urllib.parse"), MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), MovedAttribute("uses_query", "urlparse", "urllib.parse"), MovedAttribute("uses_relative", "urlparse", "urllib.parse"), ] for attr in _urllib_parse_moved_attributes: setattr(Module_six_moves_urllib_parse, attr.name, attr) del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), "moves.urllib_parse", "moves.urllib.parse") class Module_six_moves_urllib_error(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_error""" _urllib_error_moved_attributes = [ MovedAttribute("URLError", "urllib2", "urllib.error"), MovedAttribute("HTTPError", "urllib2", "urllib.error"), MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), ] for attr in _urllib_error_moved_attributes: setattr(Module_six_moves_urllib_error, attr.name, attr) del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), "moves.urllib_error", "moves.urllib.error") class Module_six_moves_urllib_request(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_request""" _urllib_request_moved_attributes = [ MovedAttribute("urlopen", "urllib2", "urllib.request"), MovedAttribute("install_opener", "urllib2", "urllib.request"), MovedAttribute("build_opener", "urllib2", "urllib.request"), MovedAttribute("pathname2url", "urllib", "urllib.request"), MovedAttribute("url2pathname", "urllib", "urllib.request"), MovedAttribute("getproxies", "urllib", "urllib.request"), MovedAttribute("Request", "urllib2", "urllib.request"), MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), MovedAttribute("BaseHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), MovedAttribute("FileHandler", "urllib2", "urllib.request"), MovedAttribute("FTPHandler", "urllib2", "urllib.request"), MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), MovedAttribute("urlretrieve", "urllib", "urllib.request"), MovedAttribute("urlcleanup", "urllib", "urllib.request"), MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), "moves.urllib_request", "moves.urllib.request") class Module_six_moves_urllib_response(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_response""" _urllib_response_moved_attributes = [ MovedAttribute("addbase", "urllib", "urllib.response"), MovedAttribute("addclosehook", "urllib", "urllib.response"), MovedAttribute("addinfo", "urllib", "urllib.response"), MovedAttribute("addinfourl", "urllib", "urllib.response"), ] for attr in _urllib_response_moved_attributes: setattr(Module_six_moves_urllib_response, attr.name, attr) del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), "moves.urllib_response", "moves.urllib.response") class Module_six_moves_urllib_robotparser(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_robotparser""" _urllib_robotparser_moved_attributes = [ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), ] for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), "moves.urllib_robotparser", "moves.urllib.robotparser") class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") request = _importer._get_module("moves.urllib_request") response = _importer._get_module("moves.urllib_response") robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): return ['parse', 'error', 'request', 'response', 'robotparser'] _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib") def add_move(move): """Add an item to six.moves.""" setattr(_MovedItems, move.name, move) def remove_move(name): """Remove item from six.moves.""" try: delattr(_MovedItems, name) except AttributeError: try: del moves.__dict__[name] except KeyError: raise AttributeError("no such move, %r" % (name,)) if PY3: _meth_func = "__func__" _meth_self = "__self__" _func_closure = "__closure__" _func_code = "__code__" _func_defaults = "__defaults__" _func_globals = "__globals__" else: _meth_func = "im_func" _meth_self = "im_self" _func_closure = "func_closure" _func_code = "func_code" _func_defaults = "func_defaults" _func_globals = "func_globals" try: advance_iterator = next except NameError: def advance_iterator(it): return it.next() next = advance_iterator try: callable = callable except NameError: def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: def get_unbound_function(unbound): return unbound create_bound_method = types.MethodType def create_unbound_method(func, cls): return func Iterator = object else: def get_unbound_function(unbound): return unbound.im_func def create_bound_method(func, obj): return types.MethodType(func, obj, obj.__class__) def create_unbound_method(func, cls): return types.MethodType(func, None, cls) class Iterator(object): def next(self): return type(self).__next__(self) callable = callable _add_doc(get_unbound_function, """Get the function out of a possibly unbound function""") get_method_function = operator.attrgetter(_meth_func) get_method_self = operator.attrgetter(_meth_self) get_function_closure = operator.attrgetter(_func_closure) get_function_code = operator.attrgetter(_func_code) get_function_defaults = operator.attrgetter(_func_defaults) get_function_globals = operator.attrgetter(_func_globals) if PY3: def iterkeys(d, **kw): return iter(d.keys(**kw)) def itervalues(d, **kw): return iter(d.values(**kw)) def iteritems(d, **kw): return iter(d.items(**kw)) def iterlists(d, **kw): return iter(d.lists(**kw)) viewkeys = operator.methodcaller("keys") viewvalues = operator.methodcaller("values") viewitems = operator.methodcaller("items") else: def iterkeys(d, **kw): return d.iterkeys(**kw) def itervalues(d, **kw): return d.itervalues(**kw) def iteritems(d, **kw): return d.iteritems(**kw) def iterlists(d, **kw): return d.iterlists(**kw) viewkeys = operator.methodcaller("viewkeys") viewvalues = operator.methodcaller("viewvalues") viewitems = operator.methodcaller("viewitems") _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") _add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") _add_doc(iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary.") if PY3: def b(s): return s.encode("latin-1") def u(s): return s unichr = chr import struct int2byte = struct.Struct(">B").pack del struct byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io StringIO = io.StringIO BytesIO = io.BytesIO _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" else: def b(s): return s # Workaround for standalone backslash def u(s): return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") unichr = unichr int2byte = chr def byte2int(bs): return ord(bs[0]) def indexbytes(buf, i): return ord(buf[i]) iterbytes = functools.partial(itertools.imap, ord) import StringIO StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") def assertCountEqual(self, *args, **kwargs): return getattr(self, _assertCountEqual)(*args, **kwargs) def assertRaisesRegex(self, *args, **kwargs): return getattr(self, _assertRaisesRegex)(*args, **kwargs) def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value else: def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) if sys.version_info[:2] == (3, 2): exec_("""def raise_from(value, from_value): if from_value is None: raise value raise value from from_value """) elif sys.version_info[:2] > (3, 2): exec_("""def raise_from(value, from_value): raise value from from_value """) else: def raise_from(value, from_value): raise value print_ = getattr(moves.builtins, "print", None) if print_ is None: def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) if fp is None: return def write(data): if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. if (isinstance(fp, file) and isinstance(data, unicode) and fp.encoding is not None): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: if isinstance(sep, unicode): want_unicode = True elif not isinstance(sep, str): raise TypeError("sep must be None or a string") end = kwargs.pop("end", None) if end is not None: if isinstance(end, unicode): want_unicode = True elif not isinstance(end, str): raise TypeError("end must be None or a string") if kwargs: raise TypeError("invalid keyword arguments to print()") if not want_unicode: for arg in args: if isinstance(arg, unicode): want_unicode = True break if want_unicode: newline = unicode("\n") space = unicode(" ") else: newline = "\n" space = " " if sep is None: sep = space if end is None: end = newline for i, arg in enumerate(args): if i: write(sep) write(arg) write(end) if sys.version_info[:2] < (3, 3): _print = print_ def print_(*args, **kwargs): fp = kwargs.get("file", sys.stdout) flush = kwargs.pop("flush", False) _print(*args, **kwargs) if flush and fp is not None: fp.flush() _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): def wrapper(f): f = functools.wraps(wrapped, assigned, updated)(f) f.__wrapped__ = wrapped return f return wrapper else: wraps = functools.wraps def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, 'temporary_class', (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper def python_2_unicode_compatible(klass): """ A decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: if '__str__' not in klass.__dict__: raise ValueError("@python_2_unicode_compatible cannot be applied " "to %s because it doesn't define __str__()." % klass.__name__) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass # Complete the moves implementation. # This code is at the end of this module to speed up module loading. # Turn this module into a package. __path__ = [] # required for PEP 302 and PEP 451 __package__ = __name__ # see PEP 366 @ReservedAssignment if globals().get("__spec__") is not None: __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable # Remove other six meta path importers, since they cause problems. This can # happen if six is removed from sys.modules and then reloaded. (Setuptools does # this for some reason.) if sys.meta_path: for i, importer in enumerate(sys.meta_path): # Here's some real nastiness: Another "instance" of the six module might # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. if (type(importer).__name__ == "_SixMetaPathImporter" and importer.name == __name__): del sys.meta_path[i] break del i, importer # Finally, add the importer to the meta path import hook. sys.meta_path.append(_importer) parse_type-0.5.6/tasks/docs.py000066400000000000000000000160171372661661400163450ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Provides tasks to build documentation with sphinx, etc. """ from __future__ import absolute_import, print_function import os import sys from invoke import task, Collection from invoke.util import cd from path import Path # -- TASK-LIBRARY: from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- SPHINX_LANGUAGE_DEFAULT = os.environ.get("SPHINX_LANGUAGE", "en") # ----------------------------------------------------------------------------- # UTILTITIES: # ----------------------------------------------------------------------------- def _sphinxdoc_get_language(ctx, language=None): language = language or ctx.config.sphinx.language or SPHINX_LANGUAGE_DEFAULT return language def _sphinxdoc_get_destdir(ctx, builder, language=None): if builder == "gettext": # -- CASE: not LANGUAGE-SPECIFIC destdir = Path(ctx.config.sphinx.destdir or "build")/builder else: # -- CASE: LANGUAGE-SPECIFIC: language = _sphinxdoc_get_language(ctx, language) destdir = Path(ctx.config.sphinx.destdir or "build")/builder/language return destdir # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- @task def clean(ctx, dry_run=False): """Cleanup generated document artifacts.""" basedir = ctx.sphinx.destdir or "build/docs" cleanup_dirs([basedir], dry_run=dry_run) @task(help={ "builder": "Builder to use (html, ...)", "language": "Language to use (en, ...)", "options": "Additional options for sphinx-build", }) def build(ctx, builder="html", language=None, options=""): """Build docs with sphinx-build""" language = _sphinxdoc_get_language(ctx, language) sourcedir = ctx.config.sphinx.sourcedir destdir = _sphinxdoc_get_destdir(ctx, builder, language=language) destdir = destdir.abspath() with cd(sourcedir): destdir_relative = Path(".").relpathto(destdir) command = "sphinx-build {opts} -b {builder} -D language={language} {sourcedir} {destdir}" \ .format(builder=builder, sourcedir=".", destdir=destdir_relative, language=language, opts=options) ctx.run(command) @task(help={ "builder": "Builder to use (html, ...)", "language": "Language to use (en, ...)", "options": "Additional options for sphinx-build", }) def rebuild(ctx, builder="html", language=None, options=""): """Rebuilds the docs. Perform the steps: clean, build """ clean(ctx) build(ctx, builder=builder, language=None, options=options) @task def linkcheck(ctx): """Check if all links are corect.""" build(ctx, builder="linkcheck") @task(help={"language": "Language to use (en, ...)"}) def browse(ctx, language=None): """Open documentation in web browser.""" output_dir = _sphinxdoc_get_destdir(ctx, "html", language=language) page_html = Path(output_dir)/"index.html" if not page_html.exists(): build(ctx, builder="html") assert page_html.exists() open_cmd = "open" # -- WORKS ON: MACOSX if sys.platform.startswith("win"): open_cmd = "start" ctx.run("{open} {page_html}".format(open=open_cmd, page_html=page_html)) # ctx.run('python -m webbrowser -t {page_html}'.format(page_html=page_html)) # -- DISABLED: # import webbrowser # print("Starting webbrowser with page=%s" % page_html) # webbrowser.open(str(page_html)) @task(help={ "dest": "Destination directory to save docs", "format": "Format/Builder to use (html, ...)", "language": "Language to use (en, ...)", }) # pylint: disable=redefined-builtin def save(ctx, dest="docs.html", format="html", language=None): """Save/update docs under destination directory.""" print("STEP: Generate docs in HTML format") build(ctx, builder=format, language=language) print("STEP: Save docs under %s/" % dest) source_dir = Path(_sphinxdoc_get_destdir(ctx, format, language=language)) Path(dest).rmtree_p() source_dir.copytree(dest) # -- POST-PROCESSING: Polish up. for part in [".buildinfo", ".doctrees"]: partpath = Path(dest)/part if partpath.isdir(): partpath.rmtree_p() elif partpath.exists(): partpath.remove_p() @task(help={ "language": 'Language to use, like "en" (default: "all" to build all).', }) def update_translation(ctx, language="all"): """Update sphinx-doc translation(s) messages from the "English" docs. * Generates gettext *.po files in "build/docs/gettext/" directory * Updates/generates gettext *.po per language in "docs/LOCALE/{language}/" .. note:: Afterwards, the missing message translations can be filled in. :param language: Indicate which language messages to update (or "all"). REQUIRES: * sphinx * sphinx-intl >= 0.9 .. seealso:: https://github.com/sphinx-doc/sphinx-intl """ if language == "all": # -- CASE: Process/update all support languages (translations). DEFAULT_LANGUAGES = os.environ.get("SPHINXINTL_LANGUAGE", None) if DEFAULT_LANGUAGES: # -- EXAMPLE: SPHINXINTL_LANGUAGE="de,ja" DEFAULT_LANGUAGES = DEFAULT_LANGUAGES.split(",") languages = ctx.config.sphinx.languages or DEFAULT_LANGUAGES else: # -- CASE: Process only one language (translation use case). languages = [language] # -- STEP: Generate *.po/*.pot files w/ sphinx-build -b gettext build(ctx, builder="gettext") # -- STEP: Update *.po/*.pot files w/ sphinx-intl if languages: gettext_build_dir = _sphinxdoc_get_destdir(ctx, "gettext").abspath() docs_sourcedir = ctx.config.sphinx.sourcedir languages_opts = "-l "+ " -l ".join(languages) with ctx.cd(docs_sourcedir): ctx.run("sphinx-intl update -p {gettext_dir} {languages}".format( gettext_dir=gettext_build_dir.relpath(docs_sourcedir), languages=languages_opts)) else: print("OOPS: No languages specified (use: SPHINXINTL_LANGUAGE=...)") # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- namespace = Collection(clean, rebuild, linkcheck, browse, save, update_translation) namespace.add_task(build, default=True) namespace.configure({ "sphinx": { # -- FOR TASKS: docs.build, docs.rebuild, docs.clean, ... "language": SPHINX_LANGUAGE_DEFAULT, "sourcedir": "docs", "destdir": "build/docs", # -- FOR TASK: docs.update_translation "languages": None, # -- List of language translations, like: de, ja, ... } }) # -- ADD CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_docs") cleanup_tasks.configure(namespace.configuration()) parse_type-0.5.6/tasks/py.requirements.txt000066400000000000000000000010421372661661400207460ustar00rootroot00000000000000# ============================================================================ # INVOKE PYTHON PACKAGE REQUIREMENTS: For tasks # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ invoke >= 1.2.0 path.py >= 11.5.0 pycmd six >= 1.12.0 # -- PYTHON2 BACKPORTS: pathlib; python_version <= '3.4' backports.shutil_which; python_version <= '3.3' parse_type-0.5.6/tasks/release.py000066400000000000000000000163251372661661400170370ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Tasks for releasing this project. Normal steps:: python setup.py sdist bdist_wheel twine register dist/{project}-{version}.tar.gz twine upload dist/* twine upload --skip-existing dist/* python setup.py upload # -- DEPRECATED: No longer supported -> Use RTD instead # -- DEPRECATED: python setup.py upload_docs pypi repositories: * https://pypi.python.org/pypi * https://testpypi.python.org/pypi (not working anymore) * https://test.pypi.org/legacy/ (not working anymore) Configuration file for pypi repositories: .. code-block:: init # -- FILE: $HOME/.pypirc [distutils] index-servers = pypi testpypi [pypi] # DEPRECATED: repository = https://pypi.python.org/pypi username = __USERNAME_HERE__ password: [testpypi] # DEPRECATED: repository = https://test.pypi.org/legacy username = __USERNAME_HERE__ password: .. seealso:: * https://packaging.python.org/ * https://packaging.python.org/guides/ * https://packaging.python.org/tutorials/distributing-packages/ """ from __future__ import absolute_import, print_function from invoke import Collection, task from ._tasklet_cleanup import path_glob from ._dry_run import DryRunContext # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- @task def checklist(ctx=None): # pylint: disable=unused-argument """Checklist for releasing this project.""" checklist_text = """PRE-RELEASE CHECKLIST: [ ] Everything is checked in [ ] All tests pass w/ tox RELEASE CHECKLIST: [{x1}] Bump version to new-version and tag repository (via bump_version) [{x2}] Build packages (sdist, bdist_wheel via prepare) [{x3}] Register and upload packages to testpypi repository (first) [{x4}] Verify release is OK and packages from testpypi are usable [{x5}] Register and upload packages to pypi repository [{x6}] Push last changes to Github repository POST-RELEASE CHECKLIST: [ ] Bump version to new-develop-version (via bump_version) [ ] Adapt CHANGES (if necessary) [ ] Commit latest changes to Github repository """ steps = dict(x1=None, x2=None, x3=None, x4=None, x5=None, x6=None) yesno_map = {True: "x", False: "_", None: " "} answers = {name: yesno_map[value] for name, value in steps.items()} print(checklist_text.format(**answers)) @task(name="bump_version") def bump_version(ctx, new_version, version_part=None, dry_run=False): """Bump version (to prepare a new release).""" version_part = version_part or "minor" if dry_run: ctx = DryRunContext(ctx) ctx.run("bumpversion --new-version={} {}".format(new_version, version_part)) @task(name="build", aliases=["build_packages"]) def build_packages(ctx, hide=False): """Build packages for this release.""" print("build_packages:") ctx.run("python setup.py sdist bdist_wheel", echo=True, hide=hide) @task def prepare(ctx, new_version=None, version_part=None, hide=True, dry_run=False): """Prepare the release: bump version, build packages, ...""" if new_version is not None: bump_version(ctx, new_version, version_part=version_part, dry_run=dry_run) build_packages(ctx, hide=hide) packages = ensure_packages_exist(ctx, check_only=True) print_packages(packages) # -- NOT-NEEDED: # @task(name="register") # def register_packages(ctx, repo=None, dry_run=False): # """Register release (packages) in artifact-store/repository.""" # original_ctx = ctx # if repo is None: # repo = ctx.project.repo or "pypi" # if dry_run: # ctx = DryRunContext(ctx) # packages = ensure_packages_exist(original_ctx) # print_packages(packages) # for artifact in packages: # ctx.run("twine register --repository={repo} {artifact}".format( # artifact=artifact, repo=repo)) @task def upload(ctx, repo=None, repo_url=None, dry_run=False, skip_existing=False, verbose=False): """Upload release packages to repository (artifact-store).""" if repo is None: repo = ctx.project.repo or "pypi" if repo_url is None: repo_url = ctx.project.repo_url or None original_ctx = ctx if dry_run: ctx = DryRunContext(ctx) # -- OPTIONS: opts = [] if repo_url: opts.append("--repository-url={0}".format(repo_url)) elif repo: opts.append("--repository={0}".format(repo)) if skip_existing: opts.append("--skip-existing") if verbose: opts.append("--verbose") packages = ensure_packages_exist(original_ctx) print_packages(packages) ctx.run("twine upload {opts} dist/*".format(opts=" ".join(opts))) # ctx.run("twine upload --repository={repo} dist/*".format(repo=repo)) # 2018-05-05 WORK-AROUND for new https://pypi.org/: # twine upload --repository-url=https://upload.pypi.org/legacy /dist/* # NOT-WORKING: repo_url = "https://upload.pypi.org/simple/" # # ctx.run("twine upload --repository-url={repo_url} {opts} dist/*".format( # repo_url=repo_url, opts=" ".join(opts))) # ctx.run("twine upload --repository={repo} {opts} dist/*".format( # repo=repo, opts=" ".join(opts))) # -- DEPRECATED: Use RTD instead # @task(name="upload_docs") # def upload_docs(ctx, repo=None, dry_run=False): # """Upload and publish docs. # # NOTE: Docs are built first. # """ # if repo is None: # repo = ctx.project.repo or "pypi" # if dry_run: # ctx = DryRunContext(ctx) # # ctx.run("python setup.py upload_docs") # # ----------------------------------------------------------------------------- # TASK HELPERS: # ----------------------------------------------------------------------------- def print_packages(packages): print("PACKAGES[%d]:" % len(packages)) for package in packages: package_size = package.stat().st_size package_time = package.stat().st_mtime print(" - %s (size=%s)" % (package, package_size)) def ensure_packages_exist(ctx, pattern=None, check_only=False): if pattern is None: project_name = ctx.project.name project_prefix = project_name.replace("_", "-").split("-")[0] pattern = "dist/%s*" % project_prefix packages = list(path_glob(pattern, current_dir=".")) if not packages: if check_only: message = "No artifacts found: pattern=%s" % pattern raise RuntimeError(message) else: # -- RECURSIVE-SELF-CALL: Once print("NO-PACKAGES-FOUND: Build packages first ...") build_packages(ctx, hide=True) packages = ensure_packages_exist(ctx, pattern, check_only=True) return packages # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- # DISABLED: register_packages namespace = Collection(bump_version, checklist, prepare, build_packages, upload) namespace.configure({ "project": { "repo": "pypi", "repo_url": None, } }) parse_type-0.5.6/tasks/test.py000066400000000000000000000154341372661661400163760ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Invoke test tasks. """ from __future__ import print_function import os.path import sys from invoke import task, Collection # -- TASK-LIBRARY: from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files # --------------------------------------------------------------------------- # CONSTANTS: # --------------------------------------------------------------------------- USE_BEHAVE = False # --------------------------------------------------------------------------- # TASKS # --------------------------------------------------------------------------- @task(name="all", help={ "args": "Command line args for test run.", }) def test_all(ctx, args="", options=""): """Run all tests (default).""" pytest_args = select_by_prefix(args, ctx.pytest.scopes) behave_args = None if USE_BEHAVE: behave_args = select_by_prefix(args, ctx.behave_test.scopes) pytest_should_run = not args or (args and pytest_args) behave_should_run = not args or (args and behave_args) if pytest_should_run: pytest(ctx, pytest_args, options=options) if behave_should_run and USE_BEHAVE: behave(ctx, behave_args, options=options) @task def clean(ctx, dry_run=False): """Cleanup (temporary) test artifacts.""" directories = ctx.test.clean.directories or [] files = ctx.test.clean.files or [] cleanup_dirs(directories, dry_run=dry_run) cleanup_files(files, dry_run=dry_run) @task(name="unit") def unittest(ctx, args="", options=""): """Run unit tests.""" pytest(ctx, args, options) @task def pytest(ctx, args="", options=""): """Run unit tests.""" args = args or ctx.pytest.args options = options or ctx.pytest.options ctx.run("pytest {options} {args}".format(options=options, args=args)) @task(help={ "args": "Command line args for behave", "format": "Formatter to use (progress, pretty, ...)", }) def behave(ctx, args="", format="", options=""): """Run behave tests.""" format = format or ctx.behave_test.format options = options or ctx.behave_test.options args = args or ctx.behave_test.args if os.path.exists("bin/behave"): behave_cmd = "{python} bin/behave".format(python=sys.executable) else: behave_cmd = "{python} -m behave".format(python=sys.executable) for group_args in grouped_by_prefix(args, ctx.behave_test.scopes): ctx.run("{behave} -f {format} {options} {args}".format( behave=behave_cmd, format=format, options=options, args=group_args)) @task(help={ "args": "Tests to run (empty: all)", "report": "Coverage report format to use (report, html, xml)", }) def coverage(ctx, args="", report="report", append=False): """Determine test coverage (run pytest, behave)""" append = append or ctx.coverage.append report_formats = ctx.coverage.report_formats or [] if report not in report_formats: report_formats.insert(0, report) opts = [] if append: opts.append("--append") pytest_args = select_by_prefix(args, ctx.pytest.scopes) behave_args = select_by_prefix(args, ctx.behave_test.scopes) pytest_should_run = not args or (args and pytest_args) behave_should_run = not args or (args and behave_args) and USE_BEHAVE if not args: behave_args = ctx.behave_test.args or "features" if isinstance(pytest_args, list): pytest_args = " ".join(pytest_args) if isinstance(behave_args, list): behave_args = " ".join(behave_args) # -- RUN TESTS WITH COVERAGE: if pytest_should_run: ctx.run("coverage run {options} -m pytest {args}".format( args=pytest_args, options=" ".join(opts))) if behave_should_run and USE_BEHAVE: behave_options = ctx.behave_test.coverage_options or "" os.environ["COVERAGE_PROCESS_START"] = os.path.abspath(".coveragerc") behave(ctx, args=behave_args, options=behave_options) del os.environ["COVERAGE_PROCESS_START"] # -- POST-PROCESSING: ctx.run("coverage combine") for report_format in report_formats: ctx.run("coverage {report_format}".format(report_format=report_format)) # --------------------------------------------------------------------------- # UTILITIES: # --------------------------------------------------------------------------- def select_prefix_for(arg, prefixes): for prefix in prefixes: if arg.startswith(prefix): return prefix return os.path.dirname(arg) def select_by_prefix(args, prefixes): selected = [] for arg in args.strip().split(): assert not arg.startswith("-"), "REQUIRE: arg, not options" scope = select_prefix_for(arg, prefixes) if scope: selected.append(arg) return " ".join(selected) def grouped_by_prefix(args, prefixes): """Group behave args by (directory) scope into multiple test-runs.""" group_args = [] current_scope = None for arg in args.strip().split(): assert not arg.startswith("-"), "REQUIRE: arg, not options" scope = select_prefix_for(arg, prefixes) if scope != current_scope: if group_args: # -- DETECTED GROUP-END: yield " ".join(group_args) group_args = [] current_scope = scope group_args.append(arg) if group_args: yield " ".join(group_args) # --------------------------------------------------------------------------- # TASK MANAGEMENT / CONFIGURATION # --------------------------------------------------------------------------- namespace = Collection(clean, unittest, pytest, coverage) namespace.add_task(test_all, default=True) if USE_BEHAVE: namespace.add_task(behave) namespace.configure({ "test": { "clean": { "directories": [ ".cache", "assets", # -- TEST RUNS # -- BEHAVE-SPECIFIC: "__WORKDIR__", "reports", "test_results", ], "files": [ ".coverage", ".coverage.*", # -- BEHAVE-SPECIFIC: "report.html", "rerun*.txt", "rerun*.featureset", "testrun*.json", ], }, }, "pytest": { "scopes": ["tests"], "args": "", "options": "", # -- NOTE: Overide in configfile "invoke.yaml" }, # "behave_test": behave.namespace._configuration["behave_test"], "behave_test": { "scopes": ["features"], "args": "features", "format": "progress", "options": "", # -- NOTE: Overide in configfile "invoke.yaml" "coverage_options": "", }, "coverage": { "append": False, "report_formats": ["report", "html"], }, }) # -- ADD CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_test") cleanup_tasks.configure(namespace.configuration()) parse_type-0.5.6/tests/000077500000000000000000000000001372661661400150535ustar00rootroot00000000000000parse_type-0.5.6/tests/__init__.py000066400000000000000000000000001372661661400171520ustar00rootroot00000000000000parse_type-0.5.6/tests/parse_type_test.py000077500000000000000000000122001372661661400206350ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from parse_type import TypeBuilder from enum import Enum try: import unittest2 as unittest except ImportError: import unittest # ----------------------------------------------------------------------------- # TEST SUPPORT FOR: TypeBuilder Tests # ----------------------------------------------------------------------------- # -- PROOF-OF-CONCEPT DATATYPE: def parse_number(text): return int(text) parse_number.pattern = r"\d+" # Provide better regexp pattern than default. parse_number.name = "Number" # For testing only. # -- ENUM DATATYPE: parse_yesno = TypeBuilder.make_enum({ "yes": True, "no": False, "on": True, "off": False, "true": True, "false": False, }) parse_yesno.name = "YesNo" # For testing only. # -- ENUM CLASS: class Color(Enum): red = 1 green = 2 blue = 3 parse_color = TypeBuilder.make_enum(Color) parse_color.name = "Color" # -- CHOICE DATATYPE: parse_person_choice = TypeBuilder.make_choice(["Alice", "Bob", "Charly"]) parse_person_choice.name = "PersonChoice" # For testing only. # ----------------------------------------------------------------------------- # ABSTRACT TEST CASE: # ----------------------------------------------------------------------------- class TestCase(unittest.TestCase): # -- PYTHON VERSION BACKWARD-COMPATIBILTY: if not hasattr(unittest.TestCase, "assertIsNone"): def assertIsNone(self, obj, msg=None): self.assert_(obj is None, msg) def assertIsNotNone(self, obj, msg=None): self.assert_(obj is not None, msg) class ParseTypeTestCase(TestCase): """ Common test case base class for :mod:`parse_type` tests. """ def assert_match(self, parser, text, param_name, expected): """ Check that a parser can parse the provided text and extracts the expected value for a parameter. :param parser: Parser to use :param text: Text to parse :param param_name: Name of parameter :param expected: Expected value of parameter. :raise: AssertionError on failures. """ result = parser.parse(text) self.assertIsNotNone(result) self.assertEqual(result[param_name], expected) def assert_mismatch(self, parser, text, param_name=None): """ Check that a parser cannot extract the parameter from the provided text. A parse mismatch has occured. :param parser: Parser to use :param text: Text to parse :param param_name: Name of parameter :raise: AssertionError on failures. """ result = parser.parse(text) self.assertIsNone(result) def ensure_can_parse_all_enum_values(self, parser, type_converter, schema, name): # -- ENSURE: Known enum values are correctly extracted. for value_name, value in type_converter.mappings.items(): text = schema % value_name self.assert_match(parser, text, name, value) def ensure_can_parse_all_choices(self, parser, type_converter, schema, name): transform = getattr(type_converter, "transform", None) for choice_value in type_converter.choices: text = schema % choice_value expected_value = choice_value if transform: assert callable(transform) expected_value = transform(choice_value) self.assert_match(parser, text, name, expected_value) def ensure_can_parse_all_choices2(self, parser, type_converter, schema, name): transform = getattr(type_converter, "transform", None) for index, choice_value in enumerate(type_converter.choices): text = schema % choice_value if transform: assert callable(transform) expected_value = (index, transform(choice_value)) else: expected_value = (index, choice_value) self.assert_match(parser, text, name, expected_value) # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_builder.py000077500000000000000000000557121372661661400201270ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test suite for parse_type.py REQUIRES: parse >= 1.8.4 ('pattern' attribute support) """ from __future__ import absolute_import import re import unittest import parse from .parse_type_test import ParseTypeTestCase from .parse_type_test \ import parse_number, parse_yesno, parse_person_choice, parse_color, Color from parse_type import TypeBuilder, build_type_dict from enum import Enum # ----------------------------------------------------------------------------- # TEST CASE: TestTypeBuilder4Enum # ----------------------------------------------------------------------------- class TestTypeBuilder4Enum(ParseTypeTestCase): TYPE_CONVERTERS = [ parse_yesno ] def test_parse_enum_yesno(self): extra_types = build_type_dict([ parse_yesno ]) schema = "Answer: {answer:YesNo}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.ensure_can_parse_all_enum_values(parser, parse_yesno, "Answer: %s", "answer") # -- VALID: self.assert_match(parser, "Answer: yes", "answer", True) self.assert_match(parser, "Answer: no", "answer", False) # -- IGNORE-CASE: In parsing, calls type converter function !!! self.assert_match(parser, "Answer: YES", "answer", True) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __YES__", "answer") self.assert_mismatch(parser, "Answer: yes ", "answer") self.assert_mismatch(parser, "Answer: yes ZZZ", "answer") def test_make_enum_with_dict(self): parse_nword = TypeBuilder.make_enum({"one": 1, "two": 2, "three": 3}) parse_nword.name = "NumberAsWord" extra_types = build_type_dict([ parse_nword ]) schema = "Answer: {number:NumberAsWord}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.ensure_can_parse_all_enum_values(parser, parse_nword, "Answer: %s", "number") # -- VALID: self.assert_match(parser, "Answer: one", "number", 1) self.assert_match(parser, "Answer: two", "number", 2) # -- IGNORE-CASE: In parsing, calls type converter function !!! self.assert_match(parser, "Answer: THREE", "number", 3) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __one__", "number") self.assert_mismatch(parser, "Answer: one ", "number") self.assert_mismatch(parser, "Answer: one_", "number") self.assert_mismatch(parser, "Answer: one ZZZ", "number") def test_make_enum_with_enum_class(self): """ Use :meth:`parse_type.TypeBuilder.make_enum()` with enum34 classes. """ class Color(Enum): red = 1 green = 2 blue = 3 parse_color = TypeBuilder.make_enum(Color) parse_color.name = "Color" schema = "Answer: {color:Color}" parser = parse.Parser(schema, dict(Color=parse_color)) # -- PERFORM TESTS: self.ensure_can_parse_all_enum_values(parser, parse_color, "Answer: %s", "color") # -- VALID: self.assert_match(parser, "Answer: red", "color", Color.red) self.assert_match(parser, "Answer: green", "color", Color.green) self.assert_match(parser, "Answer: blue", "color", Color.blue) # -- IGNORE-CASE: In parsing, calls type converter function !!! self.assert_match(parser, "Answer: RED", "color", Color.red) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __RED__", "color") self.assert_mismatch(parser, "Answer: red ", "color") self.assert_mismatch(parser, "Answer: redx", "color") self.assert_mismatch(parser, "Answer: redx ZZZ", "color") # ----------------------------------------------------------------------------- # TEST CASE: TestTypeBuilder4Choice # ----------------------------------------------------------------------------- class TestTypeBuilder4Choice(ParseTypeTestCase): def test_parse_choice_persons(self): extra_types = build_type_dict([ parse_person_choice ]) schema = "Answer: {answer:PersonChoice}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "Answer: Alice", "answer", "Alice") self.assert_match(parser, "Answer: Bob", "answer", "Bob") self.ensure_can_parse_all_choices(parser, parse_person_choice, "Answer: %s", "answer") # -- IGNORE-CASE: In parsing, calls type converter function !!! # SKIP-WART: self.assert_match(parser, "Answer: BOB", "answer", "BOB") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __Alice__", "answer") self.assert_mismatch(parser, "Answer: Alice ", "answer") self.assert_mismatch(parser, "Answer: Alice ZZZ", "answer") def test_make_choice(self): parse_choice = TypeBuilder.make_choice(["one", "two", "three"]) parse_choice.name = "NumberWordChoice" extra_types = build_type_dict([ parse_choice ]) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "Answer: one", "answer", "one") self.assert_match(parser, "Answer: two", "answer", "two") self.ensure_can_parse_all_choices(parser, parse_choice, "Answer: %s", "answer") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __one__", "answer") self.assert_mismatch(parser, "Answer: one ", "answer") self.assert_mismatch(parser, "Answer: one ZZZ", "answer") def test_make_choice__anycase_accepted_case_sensitity(self): # -- NOTE: strict=False => Disable errors due to case-mismatch. parse_choice = TypeBuilder.make_choice(["one", "two", "three"], strict=False) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice)) # -- PERFORM TESTS: # NOTE: Parser uses re.IGNORECASE flag => Any case accepted. self.assert_match(parser, "Answer: one", "answer", "one") self.assert_match(parser, "Answer: TWO", "answer", "TWO") self.assert_match(parser, "Answer: Three", "answer", "Three") def test_make_choice__samecase_match_or_error(self): # -- NOTE: strict=True => Enable errors due to case-mismatch. parse_choice = TypeBuilder.make_choice(["One", "TWO", "three"], strict=True) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice)) # -- PERFORM TESTS: Case matches. # NOTE: Parser uses re.IGNORECASE flag => Any case accepted. self.assert_match(parser, "Answer: One", "answer", "One") self.assert_match(parser, "Answer: TWO", "answer", "TWO") self.assert_match(parser, "Answer: three", "answer", "three") # -- PERFORM TESTS: EXACT-CASE MISMATCH case_mismatch_input_data = ["one", "ONE", "Two", "two", "Three" ] for input_value in case_mismatch_input_data: input_text = "Answer: %s" % input_value with self.assertRaises(ValueError): parser.parse(input_text) def test_make_choice__anycase_accepted_lowercase_enforced(self): # -- NOTE: strict=True => Enable errors due to case-mismatch. parse_choice = TypeBuilder.make_choice(["one", "two", "three"], transform=lambda x: x.lower(), strict=True) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice)) # -- PERFORM TESTS: # NOTE: Parser uses re.IGNORECASE flag # => Any case accepted, but result is in lower case. self.assert_match(parser, "Answer: one", "answer", "one") self.assert_match(parser, "Answer: TWO", "answer", "two") self.assert_match(parser, "Answer: Three", "answer", "three") def test_make_choice__with_transform(self): transform = lambda x: x.upper() parse_choice = TypeBuilder.make_choice(["ONE", "two", "Three"], transform) self.assertSequenceEqual(parse_choice.choices, ["ONE", "TWO", "THREE"]) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice)) # -- PERFORM TESTS: self.assert_match(parser, "Answer: one", "answer", "ONE") self.assert_match(parser, "Answer: two", "answer", "TWO") self.ensure_can_parse_all_choices(parser, parse_choice, "Answer: %s", "answer") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __one__", "answer") self.assert_mismatch(parser, "Answer: one ", "answer") self.assert_mismatch(parser, "Answer: one ZZZ", "answer") def test_make_choice2(self): # -- strict=False: Disable errors due to case mismatch. parse_choice2 = TypeBuilder.make_choice2(["zero", "one", "two"], strict=False) parse_choice2.name = "NumberWordChoice2" extra_types = build_type_dict([ parse_choice2 ]) schema = "Answer: {answer:NumberWordChoice2}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "Answer: zero", "answer", (0, "zero")) self.assert_match(parser, "Answer: one", "answer", (1, "one")) self.assert_match(parser, "Answer: two", "answer", (2, "two")) self.ensure_can_parse_all_choices2(parser, parse_choice2, "Answer: %s", "answer") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Answer: __one__", "answer") self.assert_mismatch(parser, "Answer: one ", "answer") self.assert_mismatch(parser, "Answer: one ZZZ", "answer") def test_make_choice2__with_transform(self): transform = lambda x: x.lower() parse_choice2 = TypeBuilder.make_choice2(["ZERO", "one", "Two"], transform=transform) self.assertSequenceEqual(parse_choice2.choices, ["zero", "one", "two"]) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice2)) # -- PERFORM TESTS: # NOTE: Parser uses re.IGNORECASE => Any case is accepted. self.assert_match(parser, "Answer: zERO", "answer", (0, "zero")) self.assert_match(parser, "Answer: ONE", "answer", (1, "one")) self.assert_match(parser, "Answer: Two", "answer", (2, "two")) def test_make_choice2__samecase_match_or_error(self): # -- NOTE: strict=True => Enable errors due to case-mismatch. parse_choice2 = TypeBuilder.make_choice2(["Zero", "one", "TWO"], strict=True) schema = "Answer: {answer:NumberWordChoice}" parser = parse.Parser(schema, dict(NumberWordChoice=parse_choice2)) # -- PERFORM TESTS: Case matches. # NOTE: Parser uses re.IGNORECASE flag => Any case accepted. self.assert_match(parser, "Answer: Zero", "answer", (0, "Zero")) self.assert_match(parser, "Answer: one", "answer", (1, "one")) self.assert_match(parser, "Answer: TWO", "answer", (2, "TWO")) # -- PERFORM TESTS: EXACT-CASE MISMATCH case_mismatch_input_data = ["zero", "ZERO", "One", "ONE", "two" ] for input_value in case_mismatch_input_data: input_text = "Answer: %s" % input_value with self.assertRaises(ValueError): parser.parse(input_text) # ----------------------------------------------------------------------------- # TEST CASE: TestTypeBuilder4Variant # ----------------------------------------------------------------------------- class TestTypeBuilder4Variant(ParseTypeTestCase): TYPE_CONVERTERS = [ parse_number, parse_yesno ] def check_parse_variant_number_or_yesno(self, parse_variant, with_ignorecase=True): schema = "Variant: {variant:YesNo_or_Number}" parser = parse.Parser(schema, dict(YesNo_or_Number=parse_variant)) # -- TYPE 1: YesNo self.assert_match(parser, "Variant: yes", "variant", True) self.assert_match(parser, "Variant: no", "variant", False) # -- IGNORECASE problem => re_opts if with_ignorecase: self.assert_match(parser, "Variant: YES", "variant", True) # -- TYPE 2: Number self.assert_match(parser, "Variant: 0", "variant", 0) self.assert_match(parser, "Variant: 1", "variant", 1) self.assert_match(parser, "Variant: 12", "variant", 12) self.assert_match(parser, "Variant: 42", "variant", 42) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Variant: __YES__") self.assert_mismatch(parser, "Variant: yes ") self.assert_mismatch(parser, "Variant: yes ZZZ") self.assert_mismatch(parser, "Variant: -1") # -- PERFORM TESTS: self.ensure_can_parse_all_enum_values(parser, parse_yesno, "Variant: %s", "variant") def test_make_variant__uncompiled(self): type_converters = [parse_yesno, parse_number] parse_variant1 = TypeBuilder.make_variant(type_converters) self.check_parse_variant_number_or_yesno(parse_variant1) def test_make_variant__compiled(self): # -- REVERSED ORDER VARIANT: type_converters = [parse_number, parse_yesno] parse_variant2 = TypeBuilder.make_variant(type_converters, compiled=True) self.check_parse_variant_number_or_yesno(parse_variant2) def test_make_variant__with_re_opts_0(self): # -- SKIP: IGNORECASE checks which would raise an error in strict mode. type_converters = [parse_number, parse_yesno] parse_variant3 = TypeBuilder.make_variant(type_converters, re_opts=0) self.check_parse_variant_number_or_yesno(parse_variant3, with_ignorecase=False) def test_make_variant__with_re_opts_IGNORECASE(self): type_converters = [parse_number, parse_yesno] parse_variant3 = TypeBuilder.make_variant(type_converters, re_opts=re.IGNORECASE) self.check_parse_variant_number_or_yesno(parse_variant3) def test_make_variant__with_strict(self): # -- SKIP: IGNORECASE checks which would raise an error in strict mode. type_converters = [parse_number, parse_yesno] parse_variant = TypeBuilder.make_variant(type_converters, strict=True) self.check_parse_variant_number_or_yesno(parse_variant, with_ignorecase=False) def test_make_variant__with_strict_raises_error_on_case_mismatch(self): # -- NEEDS: # * re_opts=0 (IGNORECASE disabled) # * strict=True, allow that an error is raised type_converters = [parse_number, parse_yesno] parse_variant = TypeBuilder.make_variant(type_converters, strict=True, re_opts=0) schema = "Variant: {variant:YesNo_or_Number}" parser = parse.Parser(schema, dict(YesNo_or_Number=parse_variant)) self.assertRaises(AssertionError, parser.parse, "Variant: YES") def test_make_variant__without_strict_may_return_none_on_case_mismatch(self): # -- NEEDS: # * re_opts=0 (IGNORECASE disabled) # * strict=False, otherwise an error is raised type_converters = [parse_number, parse_yesno] parse_variant = TypeBuilder.make_variant(type_converters, re_opts=0, strict=False) schema = "Variant: {variant:YesNo_or_Number}" parser = parse.Parser(schema, dict(YesNo_or_Number=parse_variant)) result = parser.parse("Variant: No") self.assertNotEqual(result, None) self.assertEqual(result["variant"], None) def test_make_variant__with_strict_and_compiled_raises_error_on_case_mismatch(self): # XXX re_opts=0 seems to work differently. # -- NEEDS: # * re_opts=0 (IGNORECASE disabled) # * strict=True, allow that an error is raised type_converters = [parse_number, parse_yesno] # -- ENSURE: coverage for cornercase. parse_number.matcher = re.compile(parse_number.pattern) parse_variant = TypeBuilder.make_variant(type_converters, compiled=True, re_opts=0, strict=True) schema = "Variant: {variant:YesNo_or_Number}" parser = parse.Parser(schema, dict(YesNo_or_Number=parse_variant)) # XXX self.assertRaises(AssertionError, parser.parse, "Variant: YES") result = parser.parse("Variant: Yes") self.assertNotEqual(result, None) self.assertEqual(result["variant"], True) def test_make_variant__without_strict_and_compiled_may_return_none_on_case_mismatch(self): # XXX re_opts=0 seems to work differently. # -- NEEDS: # * re_opts=0 (IGNORECASE disabled) # * strict=False, otherwise an error is raised type_converters = [parse_number, parse_yesno] parse_variant = TypeBuilder.make_variant(type_converters, compiled=True, re_opts=0, strict=True) schema = "Variant: {variant:YesNo_or_Number}" parser = parse.Parser(schema, dict(YesNo_or_Number=parse_variant)) result = parser.parse("Variant: NO") self.assertNotEqual(result, None) self.assertEqual(result["variant"], False) def test_make_variant__with_color_or_person(self): type_converters = [parse_color, parse_person_choice] parse_variant2 = TypeBuilder.make_variant(type_converters) schema = "Variant2: {variant:Color_or_Person}" parser = parse.Parser(schema, dict(Color_or_Person=parse_variant2)) # -- TYPE 1: Color self.assert_match(parser, "Variant2: red", "variant", Color.red) self.assert_match(parser, "Variant2: blue", "variant", Color.blue) # -- TYPE 2: Person self.assert_match(parser, "Variant2: Alice", "variant", "Alice") self.assert_match(parser, "Variant2: Bob", "variant", "Bob") self.assert_match(parser, "Variant2: Charly", "variant", "Charly") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Variant2: __Alice__") self.assert_mismatch(parser, "Variant2: Alice ") self.assert_mismatch(parser, "Variant2: Alice2") self.assert_mismatch(parser, "Variant2: red2") # -- PERFORM TESTS: self.ensure_can_parse_all_enum_values(parser, parse_color, "Variant2: %s", "variant") self.ensure_can_parse_all_choices(parser, parse_person_choice, "Variant2: %s", "variant") class TestParserWithManyTypedFields(ParseTypeTestCase): parse_variant1 = TypeBuilder.make_variant([parse_number, parse_yesno]) parse_variant1.name = "Number_or_YesNo" parse_variant2 = TypeBuilder.make_variant([parse_color, parse_person_choice]) parse_variant2.name = "Color_or_PersonChoice" TYPE_CONVERTERS = [ parse_number, parse_yesno, parse_color, parse_person_choice, parse_variant1, parse_variant2, ] def test_parse_with_many_named_fields(self): type_dict = build_type_dict(self.TYPE_CONVERTERS) schema = """\ Number: {number:Number} YesNo: {answer:YesNo} Color: {color:Color} Person: {person:PersonChoice} Variant1: {variant1:Number_or_YesNo} Variant2: {variant2:Color_or_PersonChoice} """ parser = parse.Parser(schema, type_dict) text = """\ Number: 12 YesNo: yes Color: red Person: Alice Variant1: 42 Variant2: Bob """ expected = dict( number=12, answer=True, color=Color.red, person="Alice", variant1=42, variant2="Bob" ) result = parser.parse(text) self.assertIsNotNone(result) self.assertEqual(result.named, expected) def test_parse_with_many_unnamed_fields(self): type_dict = build_type_dict(self.TYPE_CONVERTERS) schema = """\ Number: {:Number} YesNo: {:YesNo} Color: {:Color} Person: {:PersonChoice} """ # -- OMIT: XFAIL, due to group_index delta counting => Parser problem. # Variant2: {:Color_or_PersonChoice} # Variant1: {:Number_or_YesNo} parser = parse.Parser(schema, type_dict) text = """\ Number: 12 YesNo: yes Color: red Person: Alice """ # SKIP: Variant2: Bob # SKIP: Variant1: 42 expected = [ 12, True, Color.red, "Alice", ] # -- SKIP: "Bob", 42 ] result = parser.parse(text) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) def test_parse_with_many_unnamed_fields_with_variants(self): type_dict = build_type_dict(self.TYPE_CONVERTERS) schema = """\ Number: {:Number} YesNo: {:YesNo} Color: {:Color} Person: {:PersonChoice} Variant2: {:Color_or_PersonChoice} Variant1: {:Number_or_YesNo} """ # -- OMIT: XFAIL, due to group_index delta counting => Parser problem. parser = parse.Parser(schema, type_dict) text = """\ Number: 12 YesNo: yes Color: red Person: Alice Variant2: Bob Variant1: 42 """ expected = [ 12, True, Color.red, "Alice", "Bob", 42 ] result = parser.parse(text) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_cardinality.py000077500000000000000000000565411372661661400210050ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test suite to test the :mod:`parse_type.cardinality` module. """ from __future__ import absolute_import from .parse_type_test import ParseTypeTestCase, parse_number from parse_type import Cardinality, TypeBuilder, build_type_dict from parse import Parser import parse import unittest # ----------------------------------------------------------------------------- # TEST CASE: TestCardinality # ----------------------------------------------------------------------------- class TestCardinality(ParseTypeTestCase): def test_enum_basics(self): assert Cardinality.optional is Cardinality.zero_or_one assert Cardinality.many0 is Cardinality.zero_or_more assert Cardinality.many is Cardinality.one_or_more def check_pattern_for_cardinality_one(self, pattern, new_pattern): expected_pattern = Cardinality.one.make_pattern(pattern) self.assertEqual(pattern, new_pattern) self.assertEqual(new_pattern, expected_pattern) def check_pattern_for_cardinality_zero_or_one(self, pattern, new_pattern): expected_pattern = Cardinality.zero_or_one.schema % pattern self.assertNotEqual(pattern, new_pattern) self.assertEqual(new_pattern, expected_pattern) def check_pattern_for_cardinality_zero_or_more(self, pattern, new_pattern): expected_pattern = Cardinality.zero_or_more.make_pattern(pattern) self.assertNotEqual(pattern, new_pattern) self.assertEqual(new_pattern, expected_pattern) def check_pattern_for_cardinality_one_or_more(self, pattern, new_pattern): expected_pattern = Cardinality.one_or_more.make_pattern(pattern) self.assertNotEqual(pattern, new_pattern) self.assertEqual(new_pattern, expected_pattern) def check_pattern_for_cardinality_optional(self, pattern, new_pattern): expected = Cardinality.optional.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_zero_or_one(pattern, new_pattern) def check_pattern_for_cardinality_many0(self, pattern, new_pattern): expected = Cardinality.many0.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_zero_or_more(pattern, new_pattern) def check_pattern_for_cardinality_many(self, pattern, new_pattern): expected = Cardinality.many.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_one_or_more(pattern, new_pattern) def test_make_pattern(self): data = [ (Cardinality.one, r"\d+", r"\d+"), (Cardinality.one, r"\w+", None), (Cardinality.zero_or_one, r"\w+", None), (Cardinality.one_or_more, r"\w+", None), (Cardinality.optional, "XXX", Cardinality.zero_or_one.make_pattern("XXX")), (Cardinality.many0, "XXX", Cardinality.zero_or_more.make_pattern("XXX")), (Cardinality.many, "XXX", Cardinality.one_or_more.make_pattern("XXX")), ] for cardinality, pattern, expected_pattern in data: if expected_pattern is None: expected_pattern = cardinality.make_pattern(pattern) new_pattern = cardinality.make_pattern(pattern) self.assertEqual(new_pattern, expected_pattern) name = cardinality.name checker = getattr(self, "check_pattern_for_cardinality_%s" % name) checker(pattern, new_pattern) def test_make_pattern_for_zero_or_one(self): patterns = [r"\d", r"\d+", r"\w+", r"XXX" ] expecteds = [r"(\d)?", r"(\d+)?", r"(\w+)?", r"(XXX)?" ] for pattern, expected in zip(patterns, expecteds): new_pattern = Cardinality.zero_or_one.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_zero_or_one(pattern, new_pattern) def test_make_pattern_for_zero_or_more(self): pattern = "XXX" expected = r"(XXX)?(\s*,\s*(XXX))*" new_pattern = Cardinality.zero_or_more.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_zero_or_more(pattern, new_pattern) def test_make_pattern_for_one_or_more(self): pattern = "XXX" expected = r"(XXX)(\s*,\s*(XXX))*" new_pattern = Cardinality.one_or_more.make_pattern(pattern) self.assertEqual(new_pattern, expected) self.check_pattern_for_cardinality_one_or_more(pattern, new_pattern) def test_is_many(self): is_many_true_valueset = set( [Cardinality.zero_or_more, Cardinality.one_or_more]) for cardinality in Cardinality: expected = cardinality in is_many_true_valueset self.assertEqual(cardinality.is_many(), expected) # ----------------------------------------------------------------------------- # TEST CASE: CardinalityTypeBuilderTest # ----------------------------------------------------------------------------- class CardinalityTypeBuilderTest(ParseTypeTestCase): def check_parse_number_with_zero_or_one(self, parse_candidate, type_name="OptionalNumber"): schema = "Optional: {number:%s}" % type_name type_dict = { "Number": parse_number, type_name: parse_candidate, } parser = parse.Parser(schema, type_dict) # -- PERFORM TESTS: self.assert_match(parser, "Optional: ", "number", None) self.assert_match(parser, "Optional: 1", "number", 1) self.assert_match(parser, "Optional: 42", "number", 42) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Optional: x", "number") # Not a Number. self.assert_mismatch(parser, "Optional: -1", "number") # Negative. self.assert_mismatch(parser, "Optional: a, b", "number") # List of ... def check_parse_number_with_optional(self, parse_candidate, type_name="OptionalNumber"): self.check_parse_number_with_zero_or_one(parse_candidate, type_name) def check_parse_number_with_zero_or_more(self, parse_candidate, type_name="Numbers0"): schema = "List: {numbers:%s}" % type_name type_dict = { type_name: parse_candidate, } parser = parse.Parser(schema, type_dict) # -- PERFORM TESTS: self.assert_match(parser, "List: ", "numbers", [ ]) self.assert_match(parser, "List: 1", "numbers", [ 1 ]) self.assert_match(parser, "List: 1, 2", "numbers", [ 1, 2 ]) self.assert_match(parser, "List: 1, 2, 3", "numbers", [ 1, 2, 3 ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: x", "numbers") # Not a Number. self.assert_mismatch(parser, "List: -1", "numbers") # Negative. self.assert_mismatch(parser, "List: 1,", "numbers") # Trailing sep. self.assert_mismatch(parser, "List: a, b", "numbers") # List of ... def check_parse_number_with_one_or_more(self, parse_candidate, type_name="Numbers"): schema = "List: {numbers:%s}" % type_name type_dict = { "Number": parse_number, type_name: parse_candidate, } parser = parse.Parser(schema, type_dict) # -- PERFORM TESTS: self.assert_match(parser, "List: 1", "numbers", [ 1 ]) self.assert_match(parser, "List: 1, 2", "numbers", [ 1, 2 ]) self.assert_match(parser, "List: 1, 2, 3", "numbers", [ 1, 2, 3 ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: ", "numbers") # Zero items. self.assert_mismatch(parser, "List: x", "numbers") # Not a Number. self.assert_mismatch(parser, "List: -1", "numbers") # Negative. self.assert_mismatch(parser, "List: 1,", "numbers") # Trailing sep. self.assert_mismatch(parser, "List: a, b", "numbers") # List of ... def check_parse_choice_with_optional(self, parse_candidate): # Choice (["red", "green", "blue"]) schema = "Optional: {color:OptionalChoiceColor}" parser = parse.Parser(schema, dict(OptionalChoiceColor=parse_candidate)) # -- PERFORM TESTS: self.assert_match(parser, "Optional: ", "color", None) self.assert_match(parser, "Optional: red", "color", "red") self.assert_match(parser, "Optional: green", "color", "green") self.assert_match(parser, "Optional: blue", "color", "blue") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Optional: r", "color") # Not a Color. self.assert_mismatch(parser, "Optional: redx", "color") # Similar. self.assert_mismatch(parser, "Optional: red, blue", "color") # List of ... def check_parse_number_with_many(self, parse_candidate, type_name="Numbers"): self.check_parse_number_with_one_or_more(parse_candidate, type_name) def check_parse_number_with_many0(self, parse_candidate, type_name="Numbers0"): self.check_parse_number_with_zero_or_more(parse_candidate, type_name) # ----------------------------------------------------------------------------- # TEST CASE: TestTypeBuilder4Cardinality # ----------------------------------------------------------------------------- class TestTypeBuilder4Cardinality(CardinalityTypeBuilderTest): def test_with_zero_or_one_basics(self): parse_opt_number = TypeBuilder.with_zero_or_one(parse_number) self.assertEqual(parse_opt_number.pattern, r"(\d+)?") def test_with_zero_or_one__number(self): parse_opt_number = TypeBuilder.with_zero_or_one(parse_number) self.check_parse_number_with_zero_or_one(parse_opt_number) def test_with_optional__number(self): # -- ALIAS FOR: zero_or_one parse_opt_number = TypeBuilder.with_optional(parse_number) self.check_parse_number_with_optional(parse_opt_number) def test_with_optional__choice(self): # -- ALIAS FOR: zero_or_one parse_color = TypeBuilder.make_choice(["red", "green", "blue"]) parse_opt_color = TypeBuilder.with_optional(parse_color) self.check_parse_choice_with_optional(parse_opt_color) def test_with_zero_or_more_basics(self): parse_numbers = TypeBuilder.with_zero_or_more(parse_number) self.assertEqual(parse_numbers.pattern, r"(\d+)?(\s*,\s*(\d+))*") def test_with_zero_or_more__number(self): parse_numbers = TypeBuilder.with_zero_or_more(parse_number) self.check_parse_number_with_zero_or_more(parse_numbers) def test_with_zero_or_more__choice(self): parse_color = TypeBuilder.make_choice(["red", "green", "blue"]) parse_colors = TypeBuilder.with_zero_or_more(parse_color) parse_colors.name = "Colors0" extra_types = build_type_dict([ parse_colors ]) schema = "List: {colors:Colors0}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: ", "colors", [ ]) self.assert_match(parser, "List: green", "colors", [ "green" ]) self.assert_match(parser, "List: red, green", "colors", [ "red", "green" ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: x", "colors") # Not a Color. self.assert_mismatch(parser, "List: black", "colors") # Unknown self.assert_mismatch(parser, "List: red,", "colors") # Trailing sep. self.assert_mismatch(parser, "List: a, b", "colors") # List of ... def test_with_one_or_more_basics(self): parse_numbers = TypeBuilder.with_one_or_more(parse_number) self.assertEqual(parse_numbers.pattern, r"(\d+)(\s*,\s*(\d+))*") def test_with_one_or_more_basics_with_other_separator(self): parse_numbers2 = TypeBuilder.with_one_or_more(parse_number, listsep=';') self.assertEqual(parse_numbers2.pattern, r"(\d+)(\s*;\s*(\d+))*") parse_numbers2 = TypeBuilder.with_one_or_more(parse_number, listsep=':') self.assertEqual(parse_numbers2.pattern, r"(\d+)(\s*:\s*(\d+))*") def test_with_one_or_more(self): parse_numbers = TypeBuilder.with_one_or_more(parse_number) self.check_parse_number_with_one_or_more(parse_numbers) def test_with_many(self): # -- ALIAS FOR: one_or_more parse_numbers = TypeBuilder.with_many(parse_number) self.check_parse_number_with_many(parse_numbers) def test_with_many0(self): # -- ALIAS FOR: one_or_more parse_numbers = TypeBuilder.with_many0(parse_number) self.check_parse_number_with_many0(parse_numbers) def test_with_one_or_more_choice(self): parse_color = TypeBuilder.make_choice(["red", "green", "blue"]) parse_colors = TypeBuilder.with_one_or_more(parse_color) parse_colors.name = "Colors" extra_types = build_type_dict([ parse_colors ]) schema = "List: {colors:Colors}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: green", "colors", [ "green" ]) self.assert_match(parser, "List: red, green", "colors", [ "red", "green" ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: ", "colors") # Zero items. self.assert_mismatch(parser, "List: x", "colors") # Not a Color. self.assert_mismatch(parser, "List: black", "colors") # Unknown self.assert_mismatch(parser, "List: red,", "colors") # Trailing sep. self.assert_mismatch(parser, "List: a, b", "colors") # List of ... def test_with_one_or_more_enum(self): parse_color = TypeBuilder.make_enum({"red": 1, "green":2, "blue": 3}) parse_colors = TypeBuilder.with_one_or_more(parse_color) parse_colors.name = "Colors" extra_types = build_type_dict([ parse_colors ]) schema = "List: {colors:Colors}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: green", "colors", [ 2 ]) self.assert_match(parser, "List: red, green", "colors", [ 1, 2 ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: ", "colors") # Zero items. self.assert_mismatch(parser, "List: x", "colors") # Not a Color. self.assert_mismatch(parser, "List: black", "colors") # Unknown self.assert_mismatch(parser, "List: red,", "colors") # Trailing sep. self.assert_mismatch(parser, "List: a, b", "colors") # List of ... def test_with_one_or_more_with_other_separator(self): parse_numbers2 = TypeBuilder.with_one_or_more(parse_number, listsep=';') parse_numbers2.name = "Numbers2" extra_types = build_type_dict([ parse_numbers2 ]) schema = "List: {numbers:Numbers2}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: 1", "numbers", [ 1 ]) self.assert_match(parser, "List: 1; 2", "numbers", [ 1, 2 ]) self.assert_match(parser, "List: 1; 2; 3", "numbers", [ 1, 2, 3 ]) def test_with_cardinality_one(self): parse_number2 = TypeBuilder.with_cardinality(Cardinality.one, parse_number) assert parse_number2 is parse_number def test_with_cardinality_zero_or_one(self): parse_opt_number = TypeBuilder.with_cardinality( Cardinality.zero_or_one, parse_number) self.check_parse_number_with_zero_or_one(parse_opt_number) def test_with_cardinality_zero_or_more(self): parse_many0_numbers = TypeBuilder.with_cardinality( Cardinality.zero_or_more, parse_number) self.check_parse_number_with_zero_or_more(parse_many0_numbers) def test_with_cardinality_one_or_more(self): parse_many_numbers = TypeBuilder.with_cardinality( Cardinality.one_or_more, parse_number) self.check_parse_number_with_one_or_more(parse_many_numbers) def test_with_cardinality_optional(self): parse_opt_number = TypeBuilder.with_cardinality( Cardinality.optional, parse_number) self.check_parse_number_with_optional(parse_opt_number) def test_with_cardinality_many0(self): parse_many0_numbers = TypeBuilder.with_cardinality( Cardinality.many0, parse_number) self.check_parse_number_with_zero_or_more(parse_many0_numbers) def test_with_cardinality_many(self): parse_many_numbers = TypeBuilder.with_cardinality( Cardinality.many, parse_number) self.check_parse_number_with_many(parse_many_numbers) def test_parse_with_optional_and_named_fields(self): parse_opt_number = TypeBuilder.with_optional(parse_number) parse_opt_number.name = "Number?" type_dict = build_type_dict([parse_opt_number, parse_number]) schema = "Numbers: {number1:Number?} {number2:Number}" parser = parse.Parser(schema, type_dict) # -- CASE: Optional number is present result = parser.parse("Numbers: 34 12") expected = dict(number1=34, number2=12) self.assertIsNotNone(result) self.assertEqual(result.named, expected) # -- CASE: Optional number is missing result = parser.parse("Numbers: 12") expected = dict(number1=None, number2=12) self.assertIsNotNone(result) self.assertEqual(result.named, expected) def test_parse_with_optional_and_unnamed_fields(self): # -- ENSURE: Cardinality.optional.group_count is correct # REQUIRES: Parser := parse_type.Parser with group_count support parse_opt_number = TypeBuilder.with_optional(parse_number) parse_opt_number.name = "Number?" type_dict = build_type_dict([parse_opt_number, parse_number]) schema = "Numbers: {:Number?} {:Number}" parser = Parser(schema, type_dict) # -- CASE: Optional number is present result = parser.parse("Numbers: 34 12") expected = (34, 12) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) # -- CASE: Optional number is missing result = parser.parse("Numbers: 12") expected = (None, 12) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) def test_parse_with_many_and_unnamed_fields(self): # -- ENSURE: Cardinality.one_or_more.group_count is correct # REQUIRES: Parser := parse_type.Parser with group_count support parse_many_numbers = TypeBuilder.with_many(parse_number) parse_many_numbers.name = "Number+" type_dict = build_type_dict([parse_many_numbers, parse_number]) schema = "Numbers: {:Number+} {:Number}" parser = Parser(schema, type_dict) # -- CASE: result = parser.parse("Numbers: 1, 2, 3 42") expected = ([1, 2, 3], 42) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) result = parser.parse("Numbers: 3 43") expected = ([ 3 ], 43) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) def test_parse_with_many0_and_unnamed_fields(self): # -- ENSURE: Cardinality.zero_or_more.group_count is correct # REQUIRES: Parser := parse_type.Parser with group_count support parse_many0_numbers = TypeBuilder.with_many0(parse_number) parse_many0_numbers.name = "Number*" type_dict = build_type_dict([parse_many0_numbers, parse_number]) schema = "Numbers: {:Number*} {:Number}" parser = Parser(schema, type_dict) # -- CASE: Optional numbers are present result = parser.parse("Numbers: 1, 2, 3 42") expected = ([1, 2, 3], 42) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) # -- CASE: Optional numbers are missing := EMPTY-LIST result = parser.parse("Numbers: 43") expected = ([ ], 43) self.assertIsNotNone(result) self.assertEqual(result.fixed, tuple(expected)) # class TestParserWithManyTypedFields(ParseTypeTestCase): #parse_variant1 = TypeBuilder.make_variant([parse_number, parse_yesno]) #parse_variant1.name = "Number_or_YesNo" #parse_variant2 = TypeBuilder.make_variant([parse_color, parse_person_choice]) #parse_variant2.name = "Color_or_PersonChoice" #TYPE_CONVERTERS = [ # parse_number, # parse_yesno, # parse_color, # parse_person_choice, # parse_variant1, # parse_variant2, #] # # def test_parse_with_many_named_fields(self): # type_dict = build_type_dict(self.TYPE_CONVERTERS) # schema = """\ #Number: {number:Number} #YesNo: {answer:YesNo} #Color: {color:Color} #Person: {person:PersonChoice} #Variant1: {variant1:Number_or_YesNo} #Variant2: {variant2:Color_or_PersonChoice} #""" # parser = parse.Parser(schema, type_dict) # # text = """\ #Number: 12 #YesNo: yes #Color: red #Person: Alice #Variant1: 42 #Variant2: Bob #""" # expected = dict( # number=12, # answer=True, # color=Color.red, # person="Alice", # variant1=42, # variant2="Bob" # ) # # result = parser.parse(text) # self.assertIsNotNone(result) # self.assertEqual(result.named, expected) # def test_parse_with_many_unnamed_fields(self): # type_dict = build_type_dict(self.TYPE_CONVERTERS) # schema = """\ #Number: {:Number} #YesNo: {:YesNo} #Color: {:Color} #Person: {:PersonChoice} #""" # # -- OMIT: XFAIL, due to group_index delta counting => Parser problem. # # Variant2: {:Color_or_PersonChoice} # # Variant1: {:Number_or_YesNo} # parser = parse.Parser(schema, type_dict) # # text = """\ #Number: 12 #YesNo: yes #Color: red #Person: Alice #""" # # SKIP: Variant2: Bob # # SKIP: Variant1: 42 # expected = [ 12, True, Color.red, "Alice", ] # -- SKIP: "Bob", 42 ] # # result = parser.parse(text) # self.assertIsNotNone(result) # self.assertEqual(result.fixed, tuple(expected)) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_cardinality_field.py000077500000000000000000000376361372661661400221540ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test experiment for parse. Add cardinality format field after type: "... {person:Person?} ..." -- CARDINALITY: Zero or one, 0..1 (optional) "... {persons:Person*} ..." -- CARDINALITY: Zero or more, 0..N (many0) "... {persons:Person+} ..." -- CARDINALITY: One or more, 1..N (many) REQUIRES: parse >= 1.5.3.1 ('pattern' attribute support and further extensions) STATUS: IDEA, working prototype with patched parse module, but not accepted. """ from __future__ import absolute_import from .parse_type_test \ import TestCase, parse_number, unittest from .test_cardinality import CardinalityTypeBuilderTest from parse_type import Cardinality from parse_type.cardinality_field \ import CardinalityField, CardinalityFieldTypeBuilder, MissingTypeError # ------------------------------------------------------------------------- # TEST CASE: TestParseTypeWithCardinalityField # ------------------------------------------------------------------------- class TestCardinalityField(TestCase): VALID_TYPE_NAMES = ["Number?", "Number*", "Number+"] INVALID_TYPE_NAMES = ["?Invalid", "Inval*d", "In+valid"] def test_pattern_chars(self): for pattern_char in CardinalityField.pattern_chars: self.assertIn(pattern_char, CardinalityField.from_char_map) def test_to_from_char_map_symmetry(self): for cardinality, char in CardinalityField.to_char_map.items(): self.assertEqual(cardinality, CardinalityField.from_char_map[char]) for char, cardinality in CardinalityField.from_char_map.items(): self.assertEqual(char, CardinalityField.to_char_map[cardinality]) def test_matches_type_name(self): for type_name in self.VALID_TYPE_NAMES: self.assertTrue(CardinalityField.matches_type(type_name)) for type_name in self.INVALID_TYPE_NAMES: self.assertFalse(CardinalityField.matches_type(type_name)) def test_split_type__with_valid_special_names(self): actual = CardinalityField.split_type("Color?") self.assertEqual(actual, ("Color", Cardinality.optional)) self.assertEqual(actual, ("Color", Cardinality.zero_or_one)) actual = CardinalityField.split_type("Color+") self.assertEqual(actual, ("Color", Cardinality.many)) self.assertEqual(actual, ("Color", Cardinality.one_or_more)) actual = CardinalityField.split_type("Color*") self.assertEqual(actual, ("Color", Cardinality.many0)) self.assertEqual(actual, ("Color", Cardinality.zero_or_more)) def test_split_type__with_valid_special_names2(self): for type_name in self.VALID_TYPE_NAMES: self.assertTrue(CardinalityField.matches_type(type_name)) cardinality_char = type_name[-1] expected_basename = type_name[:-1] expected_cardinality = CardinalityField.from_char_map[cardinality_char] expected = (expected_basename, expected_cardinality) actual = CardinalityField.split_type(type_name) self.assertEqual(actual, expected) def test_split_type__with_cardinality_one(self): actual = CardinalityField.split_type("Color") self.assertEqual(actual, ("Color", Cardinality.one)) def test_split_type__with_invalid_names(self): for type_name in self.INVALID_TYPE_NAMES: expected = (type_name, Cardinality.one) actual = CardinalityField.split_type(type_name) self.assertEqual(actual, expected) self.assertFalse(CardinalityField.matches_type(type_name)) def test_make_type__with_cardinality_one(self): expected = "Number" type_name = CardinalityField.make_type("Number", Cardinality.one) self.assertEqual(type_name, expected) self.assertFalse(CardinalityField.matches_type(type_name)) def test_make_type__with_cardinality_optional(self): expected = "Number?" type_name = CardinalityField.make_type("Number", Cardinality.optional) self.assertEqual(type_name, expected) self.assertTrue(CardinalityField.matches_type(type_name)) type_name2 = CardinalityField.make_type("Number", Cardinality.zero_or_one) self.assertEqual(type_name2, expected) self.assertEqual(type_name2, type_name) def test_make_type__with_cardinality_many(self): expected = "Number+" type_name = CardinalityField.make_type("Number", Cardinality.many) self.assertEqual(type_name, expected) self.assertTrue(CardinalityField.matches_type(type_name)) type_name2 = CardinalityField.make_type("Number", Cardinality.one_or_more) self.assertEqual(type_name2, expected) self.assertEqual(type_name2, type_name) def test_make_type__with_cardinality_many0(self): expected = "Number*" type_name = CardinalityField.make_type("Number", Cardinality.many0) self.assertEqual(type_name, expected) self.assertTrue(CardinalityField.matches_type(type_name)) type_name2 = CardinalityField.make_type("Number", Cardinality.zero_or_more) self.assertEqual(type_name2, expected) self.assertEqual(type_name2, type_name) def test_split_type2make_type__symmetry_with_valid_names(self): for type_name in self.VALID_TYPE_NAMES: primary_name, cardinality = CardinalityField.split_type(type_name) type_name2 = CardinalityField.make_type(primary_name, cardinality) self.assertEqual(type_name, type_name2) def test_split_type2make_type__symmetry_with_cardinality_one(self): for type_name in self.INVALID_TYPE_NAMES: primary_name, cardinality = CardinalityField.split_type(type_name) type_name2 = CardinalityField.make_type(primary_name, cardinality) self.assertEqual(type_name, primary_name) self.assertEqual(type_name, type_name2) self.assertEqual(cardinality, Cardinality.one) # ------------------------------------------------------------------------- # TEST CASE: # ------------------------------------------------------------------------- class TestCardinalityFieldTypeBuilder(CardinalityTypeBuilderTest): INVALID_TYPE_DICT_DATA = [ (dict(), "empty type_dict"), (dict(NumberX=parse_number), "non-empty type_dict (wrong name)"), ] # -- UTILITY METHODS: def generate_type_variants(self,type_name): for pattern_char in CardinalityField.pattern_chars: special_name = "%s%s" % (type_name.strip(), pattern_char) self.assertTrue(CardinalityField.matches_type(special_name)) yield special_name # -- METHOD: CardinalityFieldTypeBuilder.create_type_variant() def test_create_type_variant__with_many_and_type_converter(self): type_builder = CardinalityFieldTypeBuilder parse_candidate = type_builder.create_type_variant("Number+", type_converter=parse_number) self.check_parse_number_with_many(parse_candidate, "Number+") def test_create_type_variant__with_optional_and_type_dict(self): type_builder = CardinalityFieldTypeBuilder parse_candidate = type_builder.create_type_variant("Number?", dict(Number=parse_number)) self.check_parse_number_with_optional(parse_candidate, "Number?") def test_create_type_variant__with_many_and_type_dict(self): type_builder = CardinalityFieldTypeBuilder parse_candidate = type_builder.create_type_variant("Number+", dict(Number=parse_number)) self.check_parse_number_with_many(parse_candidate, "Number+") def test_create_type_variant__with_many0_and_type_dict(self): type_builder = CardinalityFieldTypeBuilder parse_candidate = type_builder.create_type_variant("Number*", dict(Number=parse_number)) self.check_parse_number_with_many0(parse_candidate, "Number*") def test_create_type_variant__can_create_all_variants(self): type_builder = CardinalityFieldTypeBuilder for special_name in self.generate_type_variants("Number"): # -- CASE: type_converter parse_candidate = type_builder.create_type_variant(special_name, parse_number) self.assertTrue(callable(parse_candidate)) # -- CASE: type_dict parse_candidate = type_builder.create_type_variant(special_name, dict(Number=parse_number)) self.assertTrue(callable(parse_candidate)) def test_create_type_variant__raises_error_with_invalid_type_name(self): type_builder = CardinalityFieldTypeBuilder for invalid_type_name in TestCardinalityField.INVALID_TYPE_NAMES: with self.assertRaises(ValueError): type_builder.create_type_variant(invalid_type_name, parse_number) def test_create_type_variant__raises_error_with_missing_primary_type(self): type_builder = CardinalityFieldTypeBuilder for special_name in self.generate_type_variants("Number"): for type_dict, description in self.INVALID_TYPE_DICT_DATA: with self.assertRaises(MissingTypeError): type_builder.create_type_variant(special_name, type_dict) # -- METHOD: CardinalityFieldTypeBuilder.create_type_variants() def test_create_type_variants__all(self): type_builder = CardinalityFieldTypeBuilder special_names = ["Number?", "Number+", "Number*"] type_dict = dict(Number=parse_number) new_types = type_builder.create_type_variants(special_names, type_dict) self.assertSequenceEqual(set(new_types.keys()), set(special_names)) self.assertEqual(len(new_types), 3) parse_candidate = new_types["Number?"] self.check_parse_number_with_optional(parse_candidate, "Number?") parse_candidate = new_types["Number+"] self.check_parse_number_with_many(parse_candidate, "Number+") parse_candidate = new_types["Number*"] self.check_parse_number_with_many0(parse_candidate, "Number*") def test_create_type_variants__raises_error_with_invalid_type_name(self): type_builder = CardinalityFieldTypeBuilder for invalid_type_name in TestCardinalityField.INVALID_TYPE_NAMES: type_dict = dict(Number=parse_number) with self.assertRaises(ValueError): type_names = [invalid_type_name] type_builder.create_type_variants(type_names, type_dict) def test_create_missing_type_variants__raises_error_with_missing_primary_type(self): type_builder = CardinalityFieldTypeBuilder for special_name in self.generate_type_variants("Number"): for type_dict, description in self.INVALID_TYPE_DICT_DATA: self.assertNotIn("Number", type_dict) with self.assertRaises(MissingTypeError): names = [special_name] type_builder.create_type_variants(names, type_dict) # -- METHOD: CardinalityFieldTypeBuilder.create_missing_type_variants() def test_create_missing_type_variants__all_missing(self): type_builder = CardinalityFieldTypeBuilder missing_names = ["Number?", "Number+", "Number*"] new_types = type_builder.create_missing_type_variants(missing_names, dict(Number=parse_number)) self.assertSequenceEqual(set(new_types.keys()), set(missing_names)) self.assertEqual(len(new_types), 3) def test_create_missing_type_variants__none_missing(self): # -- PREPARE: Create all types and store them in the type_dict. type_builder = CardinalityFieldTypeBuilder type_names = ["Number?", "Number+", "Number*"] all_type_names = ["Number", "Number?", "Number+", "Number*"] type_dict = dict(Number=parse_number) new_types = type_builder.create_missing_type_variants(type_names, type_dict) type_dict.update(new_types) self.assertSequenceEqual(set(new_types.keys()), set(type_names)) self.assertSequenceEqual(set(type_dict.keys()), set(all_type_names)) # -- TEST: All special types are already stored in the type_dict. new_types2 = type_builder.create_missing_type_variants(type_names, type_dict) self.assertEqual(len(new_types2), 0) def test_create_missing_type_variants__some_missing(self): # -- PREPARE: Create some types and store them in the type_dict. type_builder = CardinalityFieldTypeBuilder special_names = ["Number?", "Number+", "Number*"] type_names1 = ["Number?", "Number*"] type_names2 = special_names type_dict = dict(Number=parse_number) new_types = type_builder.create_missing_type_variants(type_names1, type_dict) type_dict.update(new_types) self.assertSequenceEqual(set(new_types.keys()), set(type_names1)) self.assertSequenceEqual(set(type_dict.keys()), set(["Number", "Number?", "Number*"])) # -- TEST: All special types are already stored in the type_dict. new_types2 = type_builder.create_missing_type_variants(type_names2, type_dict) self.assertEqual(len(new_types2), 1) self.assertSequenceEqual(set(new_types2.keys()), set(["Number+"])) def test_create_type_variant__raises_error_with_invalid_type_name(self): type_builder = CardinalityFieldTypeBuilder for invalid_type_name in TestCardinalityField.INVALID_TYPE_NAMES: type_dict = dict(Number=parse_number) with self.assertRaises(ValueError): type_names = [invalid_type_name] type_builder.create_missing_type_variants(type_names, type_dict) def test_create_missing_type_variants__raises_error_with_missing_primary_type(self): type_builder = CardinalityFieldTypeBuilder for special_name in self.generate_type_variants("Number"): for type_dict, description in self.INVALID_TYPE_DICT_DATA: self.assertNotIn("Number", type_dict) with self.assertRaises(MissingTypeError): names = [special_name] type_builder.create_missing_type_variants(names, type_dict) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_cardinality_field0.py000077500000000000000000000156221372661661400222230ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test experiment for parse. Add cardinality format field after type: "... {person:Person?} ..." -- CARDINALITY: Zero or one, 0..1 (optional) "... {persons:Person*} ..." -- CARDINALITY: Zero or more, 0..N (many0) "... {persons:Person+} ..." -- CARDINALITY: One or more, 1..N (many) REQUIRES: parse >= 1.5.3.1 ('pattern' attribute support and further extensions) STATUS: IDEA, working prototype with patched parse module, but not accepted. """ from __future__ import absolute_import from .parse_type_test import ParseTypeTestCase from parse_type import TypeBuilder, build_type_dict import parse import unittest ENABLED = False if ENABLED: # ------------------------------------------------------------------------- # TEST CASE: TestParseTypeWithCardinalityField # ------------------------------------------------------------------------- class TestParseTypeWithCardinalityField(ParseTypeTestCase): """ Test cardinality field part in parse type expressions, ala: "... {person:Person?} ..." -- OPTIONAL: cardinality is zero or one. "... {persons:Person*} ..." -- MANY0: cardinality is zero or more. "... {persons:Person+} ..." -- MANY: cardinality is one or more. NOTE: * TypeBuilder has a similar and slightly more flexible feature. * Cardinality field part works currently only for user-defined types. """ def test_without_cardinality_field(self): # -- IMPLCIT CARDINALITY: one # -- SETUP: parse_person = TypeBuilder.make_choice(["Alice", "Bob", "Charly"]) parse_person.name = "Person" # For testing only. extra_types = build_type_dict([ parse_person ]) schema = "One: {person:Person}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "One: Alice", "person", "Alice") self.assert_match(parser, "One: Bob", "person", "Bob") # -- PARSE MISMATCH: self.assert_mismatch(parser, "One: ", "person") # Missing. self.assert_mismatch(parser, "One: BAlice", "person") # Similar1. self.assert_mismatch(parser, "One: Boby", "person") # Similar2. self.assert_mismatch(parser, "One: a", "person") # INVALID ... def test_cardinality_field_with_zero_or_one(self): # -- SETUP: parse_person = TypeBuilder.make_choice(["Alice", "Bob", "Charly"]) parse_person.name = "Person" # For testing only. extra_types = build_type_dict([ parse_person ]) schema = "Optional: {person:Person?}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "Optional: ", "person", None) self.assert_match(parser, "Optional: Alice", "person", "Alice") self.assert_match(parser, "Optional: Bob", "person", "Bob") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Optional: Anna", "person") # Similar1. self.assert_mismatch(parser, "Optional: Boby", "person") # Similar2. self.assert_mismatch(parser, "Optional: a", "person") # INVALID ... def test_cardinality_field_with_one_or_more(self): # -- SETUP: parse_person = TypeBuilder.make_choice(["Alice", "Bob", "Charly"]) parse_person.name = "Person" # For testing only. extra_types = build_type_dict([ parse_person ]) schema = "List: {persons:Person+}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: Alice", "persons", [ "Alice" ]) self.assert_match(parser, "List: Bob", "persons", [ "Bob" ]) self.assert_match(parser, "List: Bob, Alice", "persons", [ "Bob", "Alice" ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List: ", "persons") # Zero items. self.assert_mismatch(parser, "List: BAlice", "persons") # Unknown1. self.assert_mismatch(parser, "List: Boby", "persons") # Unknown2. self.assert_mismatch(parser, "List: Alice,", "persons") # Trailing, self.assert_mismatch(parser, "List: a, b", "persons") # List of... def test_cardinality_field_with_zero_or_more(self): # -- SETUP: parse_person = TypeBuilder.make_choice(["Alice", "Bob", "Charly"]) parse_person.name = "Person" # For testing only. extra_types = build_type_dict([ parse_person ]) schema = "List: {persons:Person*}" parser = parse.Parser(schema, extra_types) # -- PERFORM TESTS: self.assert_match(parser, "List: ", "persons", [ ]) self.assert_match(parser, "List: Alice", "persons", [ "Alice" ]) self.assert_match(parser, "List: Bob", "persons", [ "Bob" ]) self.assert_match(parser, "List: Bob, Alice", "persons", [ "Bob", "Alice" ]) # -- PARSE MISMATCH: self.assert_mismatch(parser, "List:", "persons") # Too short. self.assert_mismatch(parser, "List: BAlice", "persons") # Unknown1. self.assert_mismatch(parser, "List: Boby", "persons") # Unknown2. self.assert_mismatch(parser, "List: Alice,", "persons") # Trailing, self.assert_mismatch(parser, "List: a, b", "persons") # List of... # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_cfparse.py000066400000000000000000000205451372661661400201150ustar00rootroot00000000000000# -*- coding: utf-8 -*- #!/usr/bin/env python # -*- coding: utf-8 -*- """ Test suite to test the :mod:`parse_type.cfparse` module. """ from __future__ import absolute_import from .parse_type_test import ParseTypeTestCase, parse_number, unittest from parse_type.cfparse import Parser from parse_type.cardinality_field \ import MissingTypeError, CardinalityFieldTypeBuilder # ----------------------------------------------------------------------------- # TEST CASE: # ----------------------------------------------------------------------------- class TestParser(ParseTypeTestCase): """ Test :class:`parse_type.cfparse.Parser`. Ensure that: * parser can parse fields with CardinalityField part even when these special type variants are not provided. * parser creates missing type converter variants for CardinalityFields as long as the primary type converter for cardinality=1 is provided. """ SPECIAL_FIELD_TYPES_DATA = [ ("{number1:Number?}", ["Number?"]), ("{number2:Number+}", ["Number+"]), ("{number3:Number*}", ["Number*"]), ("{number1:Number?} {number2:Number+} {number3:Number*}", ["Number?", "Number+", "Number*"]), ] def test_parser__can_parse_normal_fields(self): existing_types = dict(Number=parse_number) schema = "Number: {number:Number}" parser = Parser(schema, existing_types) self.assert_match(parser, "Number: 42", "number", 42) self.assert_match(parser, "Number: 123", "number", 123) self.assert_mismatch(parser, "Number: ") self.assert_mismatch(parser, "Number: XXX") self.assert_mismatch(parser, "Number: -123") def test_parser__can_parse_cardinality_field_optional(self): # -- CARDINALITY: 0..1 = zero_or_one = optional existing_types = dict(Number=parse_number) self.assertFalse("Number?" in existing_types) # -- ENSURE: Missing type variant is created. schema = "OptionalNumber: {number:Number?}" parser = Parser(schema, existing_types) self.assertTrue("Number?" in existing_types) # -- ENSURE: Newly created type variant is usable. self.assert_match(parser, "OptionalNumber: 42", "number", 42) self.assert_match(parser, "OptionalNumber: 123", "number", 123) self.assert_match(parser, "OptionalNumber: ", "number", None) self.assert_mismatch(parser, "OptionalNumber:") self.assert_mismatch(parser, "OptionalNumber: XXX") self.assert_mismatch(parser, "OptionalNumber: -123") def test_parser__can_parse_cardinality_field_many(self): # -- CARDINALITY: 1..* = one_or_more = many existing_types = dict(Number=parse_number) self.assertFalse("Number+" in existing_types) # -- ENSURE: Missing type variant is created. schema = "List: {numbers:Number+}" parser = Parser(schema, existing_types) self.assertTrue("Number+" in existing_types) # -- ENSURE: Newly created type variant is usable. self.assert_match(parser, "List: 42", "numbers", [42]) self.assert_match(parser, "List: 1, 2, 3", "numbers", [1, 2, 3]) self.assert_match(parser, "List: 4,5,6", "numbers", [4, 5, 6]) self.assert_mismatch(parser, "List: ") self.assert_mismatch(parser, "List:") self.assert_mismatch(parser, "List: XXX") self.assert_mismatch(parser, "List: -123") def test_parser__can_parse_cardinality_field_many_with_own_type_builder(self): # -- CARDINALITY: 1..* = one_or_more = many class MyCardinalityFieldTypeBuilder(CardinalityFieldTypeBuilder): listsep = ';' type_builder = MyCardinalityFieldTypeBuilder existing_types = dict(Number=parse_number) self.assertFalse("Number+" in existing_types) # -- ENSURE: Missing type variant is created. schema = "List: {numbers:Number+}" parser = Parser(schema, existing_types, type_builder=type_builder) self.assertTrue("Number+" in existing_types) # -- ENSURE: Newly created type variant is usable. # NOTE: Use other list separator. self.assert_match(parser, "List: 42", "numbers", [42]) self.assert_match(parser, "List: 1; 2; 3", "numbers", [1, 2, 3]) self.assert_match(parser, "List: 4;5;6", "numbers", [4, 5, 6]) self.assert_mismatch(parser, "List: ") self.assert_mismatch(parser, "List:") self.assert_mismatch(parser, "List: XXX") self.assert_mismatch(parser, "List: -123") def test_parser__can_parse_cardinality_field_many0(self): # -- CARDINALITY: 0..* = zero_or_more = many0 existing_types = dict(Number=parse_number) self.assertFalse("Number*" in existing_types) # -- ENSURE: Missing type variant is created. schema = "List0: {numbers:Number*}" parser = Parser(schema, existing_types) self.assertTrue("Number*" in existing_types) # -- ENSURE: Newly created type variant is usable. self.assert_match(parser, "List0: 42", "numbers", [42]) self.assert_match(parser, "List0: 1, 2, 3", "numbers", [1, 2, 3]) self.assert_match(parser, "List0: ", "numbers", []) self.assert_mismatch(parser, "List0:") self.assert_mismatch(parser, "List0: XXX") self.assert_mismatch(parser, "List0: -123") def test_create_missing_types__without_cardinality_fields_in_schema(self): schemas = ["{}", "{:Number}", "{number3}", "{number4:Number}", "XXX"] existing_types = {} for schema in schemas: new_types = Parser.create_missing_types(schema, existing_types) self.assertEqual(len(new_types), 0) self.assertEqual(new_types, {}) def test_create_missing_types__raises_error_if_primary_type_is_missing(self): # -- HINT: primary type is not provided in type_dict (existing_types) existing_types = {} for schema, missing_types in self.SPECIAL_FIELD_TYPES_DATA: with self.assertRaises(MissingTypeError): Parser.create_missing_types(schema, existing_types) def test_create_missing_types__if_special_types_are_missing(self): existing_types = dict(Number=parse_number) for schema, missing_types in self.SPECIAL_FIELD_TYPES_DATA: new_types = Parser.create_missing_types(schema, existing_types) self.assertSequenceEqual(set(new_types.keys()), set(missing_types)) def test_create_missing_types__if_special_types_exist(self): existing_types = dict(Number=parse_number) for schema, missing_types in self.SPECIAL_FIELD_TYPES_DATA: # -- FIRST STEP: Prepare new_types = Parser.create_missing_types(schema, existing_types) self.assertGreater(len(new_types), 0) # -- SECOND STEP: Now all needed special types should exist. existing_types2 = existing_types.copy() existing_types2.update(new_types) new_types2 = Parser.create_missing_types(schema, existing_types2) self.assertEqual(len(new_types2), 0) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_parse.py000066400000000000000000001236671372661661400176150ustar00rootroot00000000000000# -*- encoding: utf8 -*- # -- BASED-ON: https://github.com/r1chardj0n3s/parse/test_parse.py # VERSION: parse 1.17.0_POST_JE_FIX-ISSUE_121_PULL-REQUEST_122 # Same as original file but uses bundled :mod:`parse_type.parse` module # instead of :mod:`parse` module # # NOTE: Part of the tests are/were providd by jenisys. # -- ORIGINAL-CODE STARTS-HERE ------------------------------------------------ '''Test suite for parse.py This code is copyright 2011 eKit.com Inc (http://www.ekit.com/) See the end of the source file for the license of use. ''' from __future__ import absolute_import import unittest try: import unittest2 as unittest except ImportError: import unittest # -- ADAPTATION-END from datetime import datetime, time from decimal import Decimal import pickle import re # -- EXTENSION: import os PARSE_MODULE = os.environ.get("PARSE_TYPE_PARSE_MODULE", "parse") if PARSE_MODULE.startswith("parse_type"): # -- USE VENDOR MODULE: parse_type.parse (probably older that original) from parse_type import parse else: # -- USE ORIGINAL MODULE: parse import parse # -- EXTENSION-END class TestPattern(unittest.TestCase): def _test_expression(self, format, expression): self.assertEqual(parse.Parser(format)._expression, expression) def test_braces(self): # pull a simple string out of another string self._test_expression('{{ }}', r'\{ \}') def test_fixed(self): # pull a simple string out of another string self._test_expression('{}', r'(.+?)') self._test_expression('{} {}', r'(.+?) (.+?)') def test_named(self): # pull a named string out of another string self._test_expression('{name}', r'(?P.+?)') self._test_expression('{name} {other}', r'(?P.+?) (?P.+?)') def test_named_typed(self): # pull a named string out of another string self._test_expression('{name:w}', r'(?P\w+)') self._test_expression('{name:w} {other:w}', r'(?P\w+) (?P\w+)') def test_bird(self): # skip some trailing whitespace self._test_expression('{:>}', r' *(.+?)') def test_format_variety(self): def _(fmt, matches): d = parse.extract_format(fmt, {'spam': 'spam'}) for k in matches: self.assertEqual( d.get(k), matches[k], 'm["%s"]=%r, expect %r' % (k, d.get(k), matches[k]), ) for t in '%obxegfdDwWsS': _(t, dict(type=t)) _('10' + t, dict(type=t, width='10')) _('05d', dict(type='d', width='5', zero=True)) _('<', dict(align='<')) _('.<', dict(align='<', fill='.')) _('>', dict(align='>')) _('.>', dict(align='>', fill='.')) _('^', dict(align='^')) _('.^', dict(align='^', fill='.')) _('x=d', dict(type='d', align='=', fill='x')) _('d', dict(type='d')) _('ti', dict(type='ti')) _('spam', dict(type='spam')) _('.^010d', dict(type='d', width='10', align='^', fill='.', zero=True)) _('.2f', dict(type='f', precision='2')) _('10.2f', dict(type='f', width='10', precision='2')) def test_dot_separated_fields(self): # this should just work and provide the named value res = parse.parse('{hello.world}_{jojo.foo.baz}_{simple}', 'a_b_c') assert res.named['hello.world'] == 'a' assert res.named['jojo.foo.baz'] == 'b' assert res.named['simple'] == 'c' def test_dict_style_fields(self): res = parse.parse('{hello[world]}_{hello[foo][baz]}_{simple}', 'a_b_c') assert res.named['hello']['world'] == 'a' assert res.named['hello']['foo']['baz'] == 'b' assert res.named['simple'] == 'c' def test_dot_separated_fields_name_collisions(self): # this should just work and provide the named value res = parse.parse('{a_.b}_{a__b}_{a._b}_{a___b}', 'a_b_c_d') assert res.named['a_.b'] == 'a' assert res.named['a__b'] == 'b' assert res.named['a._b'] == 'c' assert res.named['a___b'] == 'd' def test_invalid_groupnames_are_handled_gracefully(self): self.assertRaises( NotImplementedError, parse.parse, "{hello['world']}", "doesn't work" ) class TestResult(unittest.TestCase): def test_fixed_access(self): r = parse.Result((1, 2), {}, None) self.assertEqual(r[0], 1) self.assertEqual(r[1], 2) self.assertRaises(IndexError, r.__getitem__, 2) self.assertRaises(KeyError, r.__getitem__, 'spam') def test_named_access(self): r = parse.Result((), {'spam': 'ham'}, None) self.assertEqual(r['spam'], 'ham') self.assertRaises(KeyError, r.__getitem__, 'ham') self.assertRaises(IndexError, r.__getitem__, 0) def test_contains(self): r = parse.Result(('cat',), {'spam': 'ham'}, None) self.assertTrue('spam' in r) self.assertTrue('cat' not in r) self.assertTrue('ham' not in r) class TestParse(unittest.TestCase): def test_no_match(self): # string does not match format self.assertEqual(parse.parse('{{hello}}', 'hello'), None) def test_nothing(self): # do no actual parsing r = parse.parse('{{hello}}', '{hello}') self.assertEqual(r.fixed, ()) self.assertEqual(r.named, {}) def test_no_evaluate_result(self): # pull a fixed value out of string match = parse.parse('hello {}', 'hello world', evaluate_result=False) r = match.evaluate_result() self.assertEqual(r.fixed, ('world',)) def test_regular_expression(self): # match an actual regular expression s = r'^(hello\s[wW]{}!+.*)$' e = s.replace('{}', 'orld') r = parse.parse(s, e) self.assertEqual(r.fixed, ('orld',)) e = s.replace('{}', '.*?') r = parse.parse(s, e) self.assertEqual(r.fixed, ('.*?',)) def test_question_mark(self): # issue9: make sure a ? in the parse string is handled correctly r = parse.parse('"{}"?', '"teststr"?') self.assertEqual(r[0], 'teststr') def test_pipe(self): # issue22: make sure a | in the parse string is handled correctly r = parse.parse('| {}', '| teststr') self.assertEqual(r[0], 'teststr') def test_unicode(self): # issue29: make sure unicode is parsable r = parse.parse('{}', u't€ststr') self.assertEqual(r[0], u't€ststr') def test_hexadecimal(self): # issue42: make sure bare hexadecimal isn't matched as "digits" r = parse.parse('{:d}', 'abcdef') self.assertIsNone(r) def test_fixed(self): # pull a fixed value out of string r = parse.parse('hello {}', 'hello world') self.assertEqual(r.fixed, ('world',)) def test_left(self): # pull left-aligned text out of string r = parse.parse('{:<} world', 'hello world') self.assertEqual(r.fixed, ('hello',)) def test_right(self): # pull right-aligned text out of string r = parse.parse('hello {:>}', 'hello world') self.assertEqual(r.fixed, ('world',)) def test_center(self): # pull center-aligned text out of string r = parse.parse('hello {:^} world', 'hello there world') self.assertEqual(r.fixed, ('there',)) def test_typed(self): # pull a named, typed values out of string r = parse.parse('hello {:d} {:w}', 'hello 12 people') self.assertEqual(r.fixed, (12, 'people')) r = parse.parse('hello {:w} {:w}', 'hello 12 people') self.assertEqual(r.fixed, ('12', 'people')) def test_precision(self): # pull a float out of a string r = parse.parse('Pi = {:.7f}', 'Pi = 3.1415926') self.assertEqual(r.fixed, (3.1415926,)) r = parse.parse('Pi/10 = {:8.5f}', 'Pi/10 = 0.31415') self.assertEqual(r.fixed, (0.31415,)) # float may have not leading zero r = parse.parse('Pi/10 = {:8.5f}', 'Pi/10 = .31415') self.assertEqual(r.fixed, (0.31415,)) r = parse.parse('Pi/10 = {:8.5f}', 'Pi/10 = -.31415') self.assertEqual(r.fixed, (-0.31415,)) def test_custom_type(self): # use a custom type r = parse.parse( '{:shouty} {:spam}', 'hello world', dict(shouty=lambda s: s.upper(), spam=lambda s: ''.join(reversed(s))), ) self.assertEqual(r.fixed, ('HELLO', 'dlrow')) r = parse.parse('{:d}', '12', dict(d=lambda s: int(s) * 2)) self.assertEqual(r.fixed, (24,)) r = parse.parse('{:d}', '12') self.assertEqual(r.fixed, (12,)) def test_typed_fail(self): # pull a named, typed values out of string self.assertEqual(parse.parse('hello {:d} {:w}', 'hello people 12'), None) def test_named(self): # pull a named value out of string r = parse.parse('hello {name}', 'hello world') self.assertEqual(r.named, {'name': 'world'}) def test_named_repeated(self): # test a name may be repeated r = parse.parse('{n} {n}', 'x x') self.assertEqual(r.named, {'n': 'x'}) def test_named_repeated_type(self): # test a name may be repeated with type conversion r = parse.parse('{n:d} {n:d}', '1 1') self.assertEqual(r.named, {'n': 1}) def test_named_repeated_fail_value(self): # test repeated name fails if value mismatches r = parse.parse('{n} {n}', 'x y') self.assertEqual(r, None) def test_named_repeated_type_fail_value(self): # test repeated name with type conversion fails if value mismatches r = parse.parse('{n:d} {n:d}', '1 2') self.assertEqual(r, None) def test_named_repeated_type_mismatch(self): # test repeated name with mismatched type self.assertRaises(parse.RepeatedNameError, parse.compile, '{n:d} {n:w}') def test_mixed(self): # pull a fixed and named values out of string r = parse.parse('hello {} {name} {} {spam}', 'hello world and other beings') self.assertEqual(r.fixed, ('world', 'other')) self.assertEqual(r.named, dict(name='and', spam='beings')) def test_named_typed(self): # pull a named, typed values out of string r = parse.parse('hello {number:d} {things}', 'hello 12 people') self.assertEqual(r.named, dict(number=12, things='people')) r = parse.parse('hello {number:w} {things}', 'hello 12 people') self.assertEqual(r.named, dict(number='12', things='people')) def test_named_aligned_typed(self): # pull a named, typed values out of string r = parse.parse('hello {number:d} {things}', 'hello 12 people') self.assertEqual(r.named, dict(number=12, things='people')) r = parse.parse('hello {number:^d} {things}', 'hello 12 people') self.assertEqual(r.named, dict(number=12, things='people')) def test_multiline(self): r = parse.parse('hello\n{}\nworld', 'hello\nthere\nworld') self.assertEqual(r.fixed[0], 'there') def test_spans(self): # test the string sections our fields come from string = 'hello world' r = parse.parse('hello {}', string) self.assertEqual(r.spans, {0: (6, 11)}) start, end = r.spans[0] self.assertEqual(string[start:end], r.fixed[0]) string = 'hello world' r = parse.parse('hello {:>}', string) self.assertEqual(r.spans, {0: (10, 15)}) start, end = r.spans[0] self.assertEqual(string[start:end], r.fixed[0]) string = 'hello 0x12 world' r = parse.parse('hello {val:x} world', string) self.assertEqual(r.spans, {'val': (6, 10)}) start, end = r.spans['val'] self.assertEqual(string[start:end], '0x%x' % r.named['val']) string = 'hello world and other beings' r = parse.parse('hello {} {name} {} {spam}', string) self.assertEqual( r.spans, {0: (6, 11), 'name': (12, 15), 1: (16, 21), 'spam': (22, 28)} ) def test_numbers(self): # pull a numbers out of a string def y(fmt, s, e, str_equals=False): p = parse.compile(fmt) r = p.parse(s) if r is None: self.fail('%r (%r) did not match %r' % (fmt, p._expression, s)) r = r.fixed[0] if str_equals: self.assertEqual( str(r), str(e), '%r found %r in %r, not %r' % (fmt, r, s, e) ) else: self.assertEqual(r, e, '%r found %r in %r, not %r' % (fmt, r, s, e)) def n(fmt, s, e): if parse.parse(fmt, s) is not None: self.fail('%r matched %r' % (fmt, s)) y('a {:d} b', 'a 0 b', 0) y('a {:d} b', 'a 12 b', 12) y('a {:5d} b', 'a 12 b', 12) y('a {:5d} b', 'a -12 b', -12) y('a {:d} b', 'a -12 b', -12) y('a {:d} b', 'a +12 b', 12) y('a {:d} b', 'a 12 b', 12) y('a {:d} b', 'a 0b1000 b', 8) y('a {:d} b', 'a 0o1000 b', 512) y('a {:d} b', 'a 0x1000 b', 4096) y('a {:d} b', 'a 0xabcdef b', 0xABCDEF) y('a {:%} b', 'a 100% b', 1) y('a {:%} b', 'a 50% b', 0.5) y('a {:%} b', 'a 50.1% b', 0.501) y('a {:n} b', 'a 100 b', 100) y('a {:n} b', 'a 1,000 b', 1000) y('a {:n} b', 'a 1.000 b', 1000) y('a {:n} b', 'a -1,000 b', -1000) y('a {:n} b', 'a 10,000 b', 10000) y('a {:n} b', 'a 100,000 b', 100000) n('a {:n} b', 'a 100,00 b', None) y('a {:n} b', 'a 100.000 b', 100000) y('a {:n} b', 'a 1.000.000 b', 1000000) y('a {:f} b', 'a 12.0 b', 12.0) y('a {:f} b', 'a -12.1 b', -12.1) y('a {:f} b', 'a +12.1 b', 12.1) y('a {:f} b', 'a .121 b', 0.121) y('a {:f} b', 'a -.121 b', -0.121) n('a {:f} b', 'a 12 b', None) y('a {:e} b', 'a 1.0e10 b', 1.0e10) y('a {:e} b', 'a .0e10 b', 0.0e10) y('a {:e} b', 'a 1.0E10 b', 1.0e10) y('a {:e} b', 'a 1.10000e10 b', 1.1e10) y('a {:e} b', 'a 1.0e-10 b', 1.0e-10) y('a {:e} b', 'a 1.0e+10 b', 1.0e10) # can't actually test this one on values 'cos nan != nan y('a {:e} b', 'a nan b', float('nan'), str_equals=True) y('a {:e} b', 'a NAN b', float('nan'), str_equals=True) y('a {:e} b', 'a inf b', float('inf')) y('a {:e} b', 'a +inf b', float('inf')) y('a {:e} b', 'a -inf b', float('-inf')) y('a {:e} b', 'a INF b', float('inf')) y('a {:e} b', 'a +INF b', float('inf')) y('a {:e} b', 'a -INF b', float('-inf')) y('a {:g} b', 'a 1 b', 1) y('a {:g} b', 'a 1e10 b', 1e10) y('a {:g} b', 'a 1.0e10 b', 1.0e10) y('a {:g} b', 'a 1.0E10 b', 1.0e10) y('a {:b} b', 'a 1000 b', 8) y('a {:b} b', 'a 0b1000 b', 8) y('a {:o} b', 'a 12345670 b', int('12345670', 8)) y('a {:o} b', 'a 0o12345670 b', int('12345670', 8)) y('a {:x} b', 'a 1234567890abcdef b', 0x1234567890ABCDEF) y('a {:x} b', 'a 1234567890ABCDEF b', 0x1234567890ABCDEF) y('a {:x} b', 'a 0x1234567890abcdef b', 0x1234567890ABCDEF) y('a {:x} b', 'a 0x1234567890ABCDEF b', 0x1234567890ABCDEF) y('a {:05d} b', 'a 00001 b', 1) y('a {:05d} b', 'a -00001 b', -1) y('a {:05d} b', 'a +00001 b', 1) y('a {:02d} b', 'a 10 b', 10) y('a {:=d} b', 'a 000012 b', 12) y('a {:x=5d} b', 'a xxx12 b', 12) y('a {:x=5d} b', 'a -xxx12 b', -12) # Test that hex numbers that ambiguously start with 0b / 0B are parsed correctly # See issue #65 (https://github.com/r1chardj0n3s/parse/issues/65) y('a {:x} b', 'a 0B b', 0xB) y('a {:x} b', 'a 0B1 b', 0xB1) y('a {:x} b', 'a 0b b', 0xB) y('a {:x} b', 'a 0b1 b', 0xB1) # Test that number signs are understood correctly y('a {:d} b', 'a -0o10 b', -8) y('a {:d} b', 'a -0b1010 b', -10) y('a {:d} b', 'a -0x1010 b', -0x1010) y('a {:o} b', 'a -10 b', -8) y('a {:b} b', 'a -1010 b', -10) y('a {:x} b', 'a -1010 b', -0x1010) y('a {:d} b', 'a +0o10 b', 8) y('a {:d} b', 'a +0b1010 b', 10) y('a {:d} b', 'a +0x1010 b', 0x1010) y('a {:o} b', 'a +10 b', 8) y('a {:b} b', 'a +1010 b', 10) y('a {:x} b', 'a +1010 b', 0x1010) def test_two_datetimes(self): r = parse.parse('a {:ti} {:ti} b', 'a 1997-07-16 2012-08-01 b') self.assertEqual(len(r.fixed), 2) self.assertEqual(r[0], datetime(1997, 7, 16)) self.assertEqual(r[1], datetime(2012, 8, 1)) def test_datetimes(self): def y(fmt, s, e, tz=None): p = parse.compile(fmt) r = p.parse(s) if r is None: self.fail('%r (%r) did not match %r' % (fmt, p._expression, s)) r = r.fixed[0] try: self.assertEqual(r, e, '%r found %r in %r, not %r' % (fmt, r, s, e)) except ValueError: self.fail('%r found %r in %r, not %r' % (fmt, r, s, e)) if tz is not None: self.assertEqual( r.tzinfo, tz, '%r found TZ %r in %r, not %r' % (fmt, r.tzinfo, s, e) ) def n(fmt, s, e): if parse.parse(fmt, s) is not None: self.fail('%r matched %r' % (fmt, s)) utc = parse.FixedTzOffset(0, 'UTC') aest = parse.FixedTzOffset(10 * 60, '+1000') tz60 = parse.FixedTzOffset(60, '+01:00') # ISO 8660 variants # YYYY-MM-DD (eg 1997-07-16) y('a {:ti} b', 'a 1997-07-16 b', datetime(1997, 7, 16)) # YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00) y('a {:ti} b', 'a 1997-07-16 19:20 b', datetime(1997, 7, 16, 19, 20, 0)) y('a {:ti} b', 'a 1997-07-16T19:20 b', datetime(1997, 7, 16, 19, 20, 0)) y( 'a {:ti} b', 'a 1997-07-16T19:20Z b', datetime(1997, 7, 16, 19, 20, tzinfo=utc), ) y( 'a {:ti} b', 'a 1997-07-16T19:20+0100 b', datetime(1997, 7, 16, 19, 20, tzinfo=tz60), ) y( 'a {:ti} b', 'a 1997-07-16T19:20+01:00 b', datetime(1997, 7, 16, 19, 20, tzinfo=tz60), ) y( 'a {:ti} b', 'a 1997-07-16T19:20 +01:00 b', datetime(1997, 7, 16, 19, 20, tzinfo=tz60), ) # YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00) y('a {:ti} b', 'a 1997-07-16 19:20:30 b', datetime(1997, 7, 16, 19, 20, 30)) y('a {:ti} b', 'a 1997-07-16T19:20:30 b', datetime(1997, 7, 16, 19, 20, 30)) y( 'a {:ti} b', 'a 1997-07-16T19:20:30Z b', datetime(1997, 7, 16, 19, 20, 30, tzinfo=utc), ) y( 'a {:ti} b', 'a 1997-07-16T19:20:30+01:00 b', datetime(1997, 7, 16, 19, 20, 30, tzinfo=tz60), ) y( 'a {:ti} b', 'a 1997-07-16T19:20:30 +01:00 b', datetime(1997, 7, 16, 19, 20, 30, tzinfo=tz60), ) # YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) y( 'a {:ti} b', 'a 1997-07-16 19:20:30.500000 b', datetime(1997, 7, 16, 19, 20, 30, 500000), ) y( 'a {:ti} b', 'a 1997-07-16T19:20:30.500000 b', datetime(1997, 7, 16, 19, 20, 30, 500000), ) y( 'a {:ti} b', 'a 1997-07-16T19:20:30.5Z b', datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=utc), ) y( 'a {:ti} b', 'a 1997-07-16T19:20:30.5+01:00 b', datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=tz60), ) aest_d = datetime(2011, 11, 21, 10, 21, 36, tzinfo=aest) dt = datetime(2011, 11, 21, 10, 21, 36) dt00 = datetime(2011, 11, 21, 10, 21) d = datetime(2011, 11, 21) # te RFC2822 e-mail format datetime y('a {:te} b', 'a Mon, 21 Nov 2011 10:21:36 +1000 b', aest_d) y('a {:te} b', 'a Mon, 21 Nov 2011 10:21:36 +10:00 b', aest_d) y('a {:te} b', 'a 21 Nov 2011 10:21:36 +1000 b', aest_d) # tg global (day/month) format datetime y('a {:tg} b', 'a 21/11/2011 10:21:36 AM +1000 b', aest_d) y('a {:tg} b', 'a 21/11/2011 10:21:36 AM +10:00 b', aest_d) y('a {:tg} b', 'a 21-11-2011 10:21:36 AM +1000 b', aest_d) y('a {:tg} b', 'a 21/11/2011 10:21:36 +1000 b', aest_d) y('a {:tg} b', 'a 21/11/2011 10:21:36 b', dt) y('a {:tg} b', 'a 21/11/2011 10:21 b', dt00) y('a {:tg} b', 'a 21-11-2011 b', d) y('a {:tg} b', 'a 21-Nov-2011 10:21:36 AM +1000 b', aest_d) y('a {:tg} b', 'a 21-November-2011 10:21:36 AM +1000 b', aest_d) # ta US (month/day) format datetime y('a {:ta} b', 'a 11/21/2011 10:21:36 AM +1000 b', aest_d) y('a {:ta} b', 'a 11/21/2011 10:21:36 AM +10:00 b', aest_d) y('a {:ta} b', 'a 11-21-2011 10:21:36 AM +1000 b', aest_d) y('a {:ta} b', 'a 11/21/2011 10:21:36 +1000 b', aest_d) y('a {:ta} b', 'a 11/21/2011 10:21:36 b', dt) y('a {:ta} b', 'a 11/21/2011 10:21 b', dt00) y('a {:ta} b', 'a 11-21-2011 b', d) y('a {:ta} b', 'a Nov-21-2011 10:21:36 AM +1000 b', aest_d) y('a {:ta} b', 'a November-21-2011 10:21:36 AM +1000 b', aest_d) y('a {:ta} b', 'a November-21-2011 b', d) # ts Linux System log format datetime y( 'a {:ts} b', 'a Nov 21 10:21:36 b', datetime(datetime.today().year, 11, 21, 10, 21, 36), ) y( 'a {:ts} b', 'a Nov 1 10:21:36 b', datetime(datetime.today().year, 11, 1, 10, 21, 36), ) y( 'a {:ts} b', 'a Nov 1 03:21:36 b', datetime(datetime.today().year, 11, 1, 3, 21, 36), ) # th HTTP log format date/time datetime y('a {:th} b', 'a 21/Nov/2011:10:21:36 +1000 b', aest_d) y('a {:th} b', 'a 21/Nov/2011:10:21:36 +10:00 b', aest_d) d = datetime(2011, 11, 21, 10, 21, 36) # tc ctime() format datetime y('a {:tc} b', 'a Mon Nov 21 10:21:36 2011 b', d) t530 = parse.FixedTzOffset(-5 * 60 - 30, '-5:30') t830 = parse.FixedTzOffset(-8 * 60 - 30, '-8:30') # tt Time time y('a {:tt} b', 'a 10:21:36 AM +1000 b', time(10, 21, 36, tzinfo=aest)) y('a {:tt} b', 'a 10:21:36 AM +10:00 b', time(10, 21, 36, tzinfo=aest)) y('a {:tt} b', 'a 10:21:36 AM b', time(10, 21, 36)) y('a {:tt} b', 'a 10:21:36 PM b', time(22, 21, 36)) y('a {:tt} b', 'a 10:21:36 b', time(10, 21, 36)) y('a {:tt} b', 'a 10:21 b', time(10, 21)) y('a {:tt} b', 'a 10:21:36 PM -5:30 b', time(22, 21, 36, tzinfo=t530)) y('a {:tt} b', 'a 10:21:36 PM -530 b', time(22, 21, 36, tzinfo=t530)) y('a {:tt} b', 'a 10:21:36 PM -05:30 b', time(22, 21, 36, tzinfo=t530)) y('a {:tt} b', 'a 10:21:36 PM -0530 b', time(22, 21, 36, tzinfo=t530)) y('a {:tt} b', 'a 10:21:36 PM -08:30 b', time(22, 21, 36, tzinfo=t830)) y('a {:tt} b', 'a 10:21:36 PM -0830 b', time(22, 21, 36, tzinfo=t830)) def test_datetime_group_count(self): # test we increment the group count correctly for datetimes r = parse.parse('{:ti} {}', '1972-01-01 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:tg} {}', '1-1-1972 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:ta} {}', '1-1-1972 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:th} {}', '21/Nov/2011:10:21:36 +1000 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:te} {}', '21 Nov 2011 10:21:36 +1000 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:tc} {}', 'Mon Nov 21 10:21:36 2011 spam') self.assertEqual(r.fixed[1], 'spam') r = parse.parse('{:tt} {}', '10:21 spam') self.assertEqual(r.fixed[1], 'spam') def test_mixed_types(self): # stress-test: pull one of everything out of a string r = parse.parse( ''' letters: {:w} non-letters: {:W} whitespace: "{:s}" non-whitespace: \t{:S}\n digits: {:d} {:d} non-digits: {:D} numbers with thousands: {:n} fixed-point: {:f} floating-point: {:e} general numbers: {:g} {:g} binary: {:b} octal: {:o} hex: {:x} ISO 8601 e.g. {:ti} RFC2822 e.g. {:te} Global e.g. {:tg} US e.g. {:ta} ctime() e.g. {:tc} HTTP e.g. {:th} time: {:tt} final value: {} ''', ''' letters: abcdef_GHIJLK non-letters: !@#%$ *^% whitespace: " \t\n" non-whitespace: \tabc\n digits: 12345 0b1011011 non-digits: abcdef numbers with thousands: 1,000 fixed-point: 100.2345 floating-point: 1.1e-10 general numbers: 1 1.1 binary: 0b1000 octal: 0o1000 hex: 0x1000 ISO 8601 e.g. 1972-01-20T10:21:36Z RFC2822 e.g. Mon, 20 Jan 1972 10:21:36 +1000 Global e.g. 20/1/1972 10:21:36 AM +1:00 US e.g. 1/20/1972 10:21:36 PM +10:30 ctime() e.g. Sun Sep 16 01:03:52 1973 HTTP e.g. 21/Nov/2011:00:07:11 +0000 time: 10:21:36 PM -5:30 final value: spam ''', ) self.assertNotEqual(r, None) self.assertEqual(r.fixed[22], 'spam') def test_mixed_type_variant(self): r = parse.parse( ''' letters: {:w} non-letters: {:W} whitespace: "{:s}" non-whitespace: \t{:S}\n digits: {:d} non-digits: {:D} numbers with thousands: {:n} fixed-point: {:f} floating-point: {:e} general numbers: {:g} {:g} binary: {:b} octal: {:o} hex: {:x} ISO 8601 e.g. {:ti} RFC2822 e.g. {:te} Global e.g. {:tg} US e.g. {:ta} ctime() e.g. {:tc} HTTP e.g. {:th} time: {:tt} final value: {} ''', ''' letters: abcdef_GHIJLK non-letters: !@#%$ *^% whitespace: " \t\n" non-whitespace: \tabc\n digits: 0xabcdef non-digits: abcdef numbers with thousands: 1.000.000 fixed-point: 0.00001 floating-point: NAN general numbers: 1.1e10 nan binary: 0B1000 octal: 0O1000 hex: 0X1000 ISO 8601 e.g. 1972-01-20T10:21:36Z RFC2822 e.g. Mon, 20 Jan 1972 10:21:36 +1000 Global e.g. 20/1/1972 10:21:36 AM +1:00 US e.g. 1/20/1972 10:21:36 PM +10:30 ctime() e.g. Sun Sep 16 01:03:52 1973 HTTP e.g. 21/Nov/2011:00:07:11 +0000 time: 10:21:36 PM -5:30 final value: spam ''', ) self.assertNotEqual(r, None) self.assertEqual(r.fixed[21], 'spam') def test_too_many_fields(self): # Python 3.5 removed the limit of 100 named groups in a regular expression, # so only test for the exception if the limit exists. try: re.compile("".join("(?P{n}-)".format(n=i) for i in range(101))) except AssertionError: p = parse.compile('{:ti}' * 15) self.assertRaises(parse.TooManyFields, p.parse, '') def test_letters(self): res = parse.parse('{:l}', '') self.assertIsNone(res) res = parse.parse('{:l}', 'sPaM') self.assertEqual(res.fixed, ('sPaM',)) res = parse.parse('{:l}', 'sP4M') self.assertIsNone(res) res = parse.parse('{:l}', 'sP_M') self.assertIsNone(res) class TestSearch(unittest.TestCase): def test_basic(self): # basic search() test r = parse.search('a {} c', ' a b c ') self.assertEqual(r.fixed, ('b',)) def test_multiline(self): # multiline search() test r = parse.search('age: {:d}\n', 'name: Rufus\nage: 42\ncolor: red\n') self.assertEqual(r.fixed, (42,)) def test_pos(self): # basic search() test r = parse.search('a {} c', ' a b c ', 2) self.assertEqual(r, None) def test_no_evaluate_result(self): match = parse.search( 'age: {:d}\n', 'name: Rufus\nage: 42\ncolor: red\n', evaluate_result=False ) r = match.evaluate_result() self.assertEqual(r.fixed, (42,)) class TestFindall(unittest.TestCase): def test_findall(self): # basic findall() test s = ''.join( r.fixed[0] for r in parse.findall(">{}<", "

    some bold text

    ") ) self.assertEqual(s, "some bold text") def test_no_evaluate_result(self): # basic findall() test s = ''.join( m.evaluate_result().fixed[0] for m in parse.findall( ">{}<", "

    some bold text

    ", evaluate_result=False ) ) self.assertEqual(s, "some bold text") def test_case_sensitivity(self): l = [r.fixed[0] for r in parse.findall("x({})x", "X(hi)X")] self.assertEqual(l, ["hi"]) l = [r.fixed[0] for r in parse.findall("x({})x", "X(hi)X", case_sensitive=True)] self.assertEqual(l, []) class TestBugs(unittest.TestCase): def test_tz_compare_to_None(self): utc = parse.FixedTzOffset(0, 'UTC') self.assertNotEqual(utc, None) self.assertNotEqual(utc, 'spam') def test_named_date_issue7(self): r = parse.parse('on {date:ti}', 'on 2012-09-17') self.assertEqual(r['date'], datetime(2012, 9, 17, 0, 0, 0)) # fix introduced regressions r = parse.parse('a {:ti} b', 'a 1997-07-16T19:20 b') self.assertEqual(r[0], datetime(1997, 7, 16, 19, 20, 0)) r = parse.parse('a {:ti} b', 'a 1997-07-16T19:20Z b') utc = parse.FixedTzOffset(0, 'UTC') self.assertEqual(r[0], datetime(1997, 7, 16, 19, 20, tzinfo=utc)) r = parse.parse('a {date:ti} b', 'a 1997-07-16T19:20Z b') self.assertEqual(r['date'], datetime(1997, 7, 16, 19, 20, tzinfo=utc)) def test_dotted_type_conversion_pull_8(self): # test pull request 8 which fixes type conversion related to dotted # names being applied correctly r = parse.parse('{a.b:d}', '1') self.assertEqual(r['a.b'], 1) r = parse.parse('{a_b:w} {a.b:d}', '1 2') self.assertEqual(r['a_b'], '1') self.assertEqual(r['a.b'], 2) def test_pm_overflow_issue16(self): r = parse.parse('Meet at {:tg}', 'Meet at 1/2/2011 12:45 PM') self.assertEqual(r[0], datetime(2011, 2, 1, 12, 45)) def test_pm_handling_issue57(self): r = parse.parse('Meet at {:tg}', 'Meet at 1/2/2011 12:15 PM') self.assertEqual(r[0], datetime(2011, 2, 1, 12, 15)) r = parse.parse('Meet at {:tg}', 'Meet at 1/2/2011 12:15 AM') self.assertEqual(r[0], datetime(2011, 2, 1, 0, 15)) def test_user_type_with_group_count_issue60(self): @parse.with_pattern(r'((\w+))', regex_group_count=2) def parse_word_and_covert_to_uppercase(text): return text.strip().upper() @parse.with_pattern(r'\d+') def parse_number(text): return int(text) # -- CASE: Use named (OK) type_map = dict(Name=parse_word_and_covert_to_uppercase, Number=parse_number) r = parse.parse( 'Hello {name:Name} {number:Number}', 'Hello Alice 42', extra_types=type_map ) self.assertEqual(r.named, dict(name='ALICE', number=42)) # -- CASE: Use unnamed/fixed (problematic) r = parse.parse( 'Hello {:Name} {:Number}', 'Hello Alice 42', extra_types=type_map ) self.assertEqual(r[0], 'ALICE') self.assertEqual(r[1], 42) def test_unmatched_brace_doesnt_match(self): r = parse.parse("{who.txt", "hello") self.assertIsNone(r) def test_pickling_bug_110(self): p = parse.compile('{a:d}') # prior to the fix, this would raise an AttributeError pickle.dumps(p) def test_search_centered_bug_112(self): r = parse.parse("{:^},{:^}", " 12 , 34 ") self.assertEqual(r[1], "34") r = parse.search("{:^},{:^}", " 12 , 34 ") self.assertEqual(r[1], "34") def test_search_left_align_bug_112(self): r = parse.parse("{:<},{:<}", "12 ,34 ") self.assertEqual(r[1], "34") r = parse.search("{:<},{:<}", "12 ,34 ") self.assertEqual(r[1], "34") # ----------------------------------------------------------------------------- # TEST SUPPORT FOR: TestParseType # ----------------------------------------------------------------------------- class TestParseType(unittest.TestCase): def assert_match(self, parser, text, param_name, expected): result = parser.parse(text) self.assertEqual(result[param_name], expected) def assert_mismatch(self, parser, text, param_name): result = parser.parse(text) self.assertTrue(result is None) def assert_fixed_match(self, parser, text, expected): result = parser.parse(text) self.assertEqual(result.fixed, expected) def assert_fixed_mismatch(self, parser, text): result = parser.parse(text) self.assertEqual(result, None) def test_pattern_should_be_used(self): def parse_number(text): return int(text) parse_number.pattern = r"\d+" parse_number.name = "Number" # For testing only. extra_types = {parse_number.name: parse_number} format = "Value is {number:Number} and..." parser = parse.Parser(format, extra_types) self.assert_match(parser, "Value is 42 and...", "number", 42) self.assert_match(parser, "Value is 00123 and...", "number", 123) self.assert_mismatch(parser, "Value is ALICE and...", "number") self.assert_mismatch(parser, "Value is -123 and...", "number") def test_pattern_should_be_used2(self): def parse_yesno(text): return parse_yesno.mapping[text.lower()] parse_yesno.mapping = { "yes": True, "no": False, "on": True, "off": False, "true": True, "false": False, } parse_yesno.pattern = r"|".join(parse_yesno.mapping.keys()) parse_yesno.name = "YesNo" # For testing only. extra_types = {parse_yesno.name: parse_yesno} format = "Answer: {answer:YesNo}" parser = parse.Parser(format, extra_types) # -- ENSURE: Known enum values are correctly extracted. for value_name, value in parse_yesno.mapping.items(): text = "Answer: %s" % value_name self.assert_match(parser, text, "answer", value) # -- IGNORE-CASE: In parsing, calls type converter function !!! self.assert_match(parser, "Answer: YES", "answer", True) self.assert_mismatch(parser, "Answer: __YES__", "answer") def test_with_pattern(self): ab_vals = dict(a=1, b=2) @parse.with_pattern(r'[ab]') def ab(text): return ab_vals[text] parser = parse.Parser('test {result:ab}', {'ab': ab}) self.assert_match(parser, 'test a', 'result', 1) self.assert_match(parser, 'test b', 'result', 2) self.assert_mismatch(parser, "test c", "result") def test_with_pattern_and_regex_group_count(self): # -- SPECIAL-CASE: Regex-grouping is used in user-defined type # NOTE: Missing or wroung regex_group_counts cause problems # with parsing following params. @parse.with_pattern(r'(meter|kilometer)', regex_group_count=1) def parse_unit(text): return text.strip() @parse.with_pattern(r'\d+') def parse_number(text): return int(text) type_converters = dict(Number=parse_number, Unit=parse_unit) # -- CASE: Unnamed-params (affected) parser = parse.Parser('test {:Unit}-{:Number}', type_converters) self.assert_fixed_match(parser, 'test meter-10', ('meter', 10)) self.assert_fixed_match(parser, 'test kilometer-20', ('kilometer', 20)) self.assert_fixed_mismatch(parser, 'test liter-30') # -- CASE: Named-params (uncritical; should not be affected) # REASON: Named-params have additional, own grouping. parser2 = parse.Parser('test {unit:Unit}-{value:Number}', type_converters) self.assert_match(parser2, 'test meter-10', 'unit', 'meter') self.assert_match(parser2, 'test meter-10', 'value', 10) self.assert_match(parser2, 'test kilometer-20', 'unit', 'kilometer') self.assert_match(parser2, 'test kilometer-20', 'value', 20) self.assert_mismatch(parser2, 'test liter-30', 'unit') def test_with_pattern_and_wrong_regex_group_count_raises_error(self): # -- SPECIAL-CASE: # Regex-grouping is used in user-defined type, but wrong value is provided. @parse.with_pattern(r'(meter|kilometer)', regex_group_count=1) def parse_unit(text): return text.strip() @parse.with_pattern(r'\d+') def parse_number(text): return int(text) # -- CASE: Unnamed-params (affected) BAD_REGEX_GROUP_COUNTS_AND_ERRORS = [ (None, ValueError), (0, ValueError), (2, IndexError), ] for bad_regex_group_count, error_class in BAD_REGEX_GROUP_COUNTS_AND_ERRORS: parse_unit.regex_group_count = bad_regex_group_count # -- OVERRIDE-HERE type_converters = dict(Number=parse_number, Unit=parse_unit) parser = parse.Parser('test {:Unit}-{:Number}', type_converters) self.assertRaises(error_class, parser.parse, 'test meter-10') def test_with_pattern_and_regex_group_count_is_none(self): # -- CORNER-CASE: Increase code-coverage. data_values = dict(a=1, b=2) @parse.with_pattern(r'[ab]') def parse_data(text): return data_values[text] parse_data.regex_group_count = None # ENFORCE: None # -- CASE: Unnamed-params parser = parse.Parser('test {:Data}', {'Data': parse_data}) self.assert_fixed_match(parser, 'test a', (1,)) self.assert_fixed_match(parser, 'test b', (2,)) self.assert_fixed_mismatch(parser, 'test c') # -- CASE: Named-params parser2 = parse.Parser('test {value:Data}', {'Data': parse_data}) self.assert_match(parser2, 'test a', 'value', 1) self.assert_match(parser2, 'test b', 'value', 2) self.assert_mismatch(parser2, 'test c', 'value') def test_case_sensitivity(self): r = parse.parse('SPAM {} SPAM', 'spam spam spam') self.assertEqual(r[0], 'spam') self.assertEqual( parse.parse('SPAM {} SPAM', 'spam spam spam', case_sensitive=True), None ) def test_decimal_value(self): value = Decimal('5.5') str_ = 'test {}'.format(value) parser = parse.Parser('test {:F}') self.assertEqual(parser.parse(str_)[0], value) def test_width_str(self): res = parse.parse('{:.2}{:.2}', 'look') self.assertEqual(res.fixed, ('lo', 'ok')) res = parse.parse('{:2}{:2}', 'look') self.assertEqual(res.fixed, ('lo', 'ok')) res = parse.parse('{:4}{}', 'look at that') self.assertEqual(res.fixed, ('look', ' at that')) def test_width_constraints(self): res = parse.parse('{:4}', 'looky') self.assertEqual(res.fixed, ('looky',)) res = parse.parse('{:4.4}', 'looky') self.assertIsNone(res) res = parse.parse('{:4.4}', 'ook') self.assertIsNone(res) res = parse.parse('{:4}{:.4}', 'look at that') self.assertEqual(res.fixed, ('look at ', 'that')) def test_width_multi_int(self): res = parse.parse('{:02d}{:02d}', '0440') self.assertEqual(res.fixed, (4, 40)) res = parse.parse('{:03d}{:d}', '04404') self.assertEqual(res.fixed, (44, 4)) def test_width_empty_input(self): res = parse.parse('{:.2}', '') self.assertIsNone(res) res = parse.parse('{:2}', 'l') self.assertIsNone(res) res = parse.parse('{:2d}', '') self.assertIsNone(res) def test_int_convert_stateless_base(self): parser = parse.Parser("{:d}") self.assertEqual(parser.parse("1234")[0], 1234) self.assertEqual(parser.parse("0b1011")[0], 0b1011) if __name__ == '__main__': unittest.main() # Copyright (c) 2011 eKit.com Inc (http://www.ekit.com/) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # vim: set filetype=python ts=4 sw=4 et si tw=75 parse_type-0.5.6/tests/test_parse_decorator.py000077500000000000000000000130701372661661400216440ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # pylint: disable=invalid-name, missing-docstring, too-few-public-methods """ Integrated into :mod:`parse` module. """ from __future__ import absolute_import import unittest import parse from parse_type import build_type_dict from .parse_type_test import ParseTypeTestCase # ----------------------------------------------------------------------------- # TEST CASE: TestParseTypeWithPatternDecorator # ----------------------------------------------------------------------------- class TestParseTypeWithPatternDecorator(ParseTypeTestCase): r""" Test the pattern decorator for type-converter (parse_type) functions. >>> def parse_number(text): ... return int(text) >>> parse_number.pattern = r"\d+" is equivalent to: >>> import parse >>> @parse.with_pattern(r"\d+") ... def parse_number(text): ... return int(text) >>> assert hasattr(parse_number, "pattern") >>> assert parse_number.pattern == r"\d+" """ def assert_decorated_with_pattern(self, func, expected_pattern): self.assertTrue(callable(func)) self.assertTrue(hasattr(func, "pattern")) self.assertEqual(func.pattern, expected_pattern) def assert_converter_call(self, func, text, expected_value): value = func(text) self.assertEqual(value, expected_value) # -- TESTS: def test_function_with_pattern_decorator(self): @parse.with_pattern(r"\d+") def parse_number(text): return int(text) self.assert_decorated_with_pattern(parse_number, r"\d+") self.assert_converter_call(parse_number, "123", 123) def test_classmethod_with_pattern_decorator(self): choice_pattern = r"Alice|Bob|Charly" class C(object): @classmethod @parse.with_pattern(choice_pattern) def parse_choice(cls, text): return text self.assert_decorated_with_pattern(C.parse_choice, choice_pattern) self.assert_converter_call(C.parse_choice, "Alice", "Alice") def test_staticmethod_with_pattern_decorator(self): choice_pattern = r"Alice|Bob|Charly" class S(object): @staticmethod @parse.with_pattern(choice_pattern) def parse_choice(text): return text self.assert_decorated_with_pattern(S.parse_choice, choice_pattern) self.assert_converter_call(S.parse_choice, "Bob", "Bob") def test_decorated_function_with_parser(self): # -- SETUP: @parse.with_pattern(r"\d+") def parse_number(text): return int(text) parse_number.name = "Number" #< For test automation. more_types = build_type_dict([parse_number]) schema = "Test: {number:Number}" parser = parse.Parser(schema, more_types) # -- PERFORM TESTS: # pylint: disable=bad-whitespace self.assert_match(parser, "Test: 1", "number", 1) self.assert_match(parser, "Test: 42", "number", 42) self.assert_match(parser, "Test: 123", "number", 123) # -- PARSE MISMATCH: self.assert_mismatch(parser, "Test: x", "number") # Not a Number. self.assert_mismatch(parser, "Test: -1", "number") # Negative. self.assert_mismatch(parser, "Test: a, b", "number") # List of ... def test_decorated_classmethod_with_parser(self): # -- SETUP: class C(object): @classmethod @parse.with_pattern(r"Alice|Bob|Charly") def parse_person(cls, text): return text more_types = {"Person": C.parse_person} schema = "Test: {person:Person}" parser = parse.Parser(schema, more_types) # -- PERFORM TESTS: # pylint: disable=bad-whitespace self.assert_match(parser, "Test: Alice", "person", "Alice") self.assert_match(parser, "Test: Bob", "person", "Bob") # -- PARSE MISMATCH: self.assert_mismatch(parser, "Test: ", "person") # Missing. self.assert_mismatch(parser, "Test: BAlice", "person") # Similar1. self.assert_mismatch(parser, "Test: Boby", "person") # Similar2. self.assert_mismatch(parser, "Test: a", "person") # INVALID ... # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tests/test_parse_number.py000066400000000000000000000033631372661661400211530ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Additional unit tests for the :mod`parse` module. Related to auto-detection of number base (base=10, 2, 8, 16). """ from __future__ import absolute_import, print_function import pytest import parse parse_version = parse.__version__ print("USING: parse-%s" % parse_version) if parse_version in ("1.17.0", "1.16.0"): print("USING: parse_type.parse (INSTEAD)") from parse_type import parse def assert_parse_number_with_format_d(text, expected): parser = parse.Parser("{value:d}") result = parser.parse(text) assert result.named == dict(value=expected) @pytest.mark.parametrize("text, expected", [ ("123", 123) ]) def test_parse_number_with_base10(text, expected): assert_parse_number_with_format_d(text, expected) @pytest.mark.parametrize("text, expected", [ ("0b0", 0), ("0b1011", 11), ]) def test_parse_number_with_base2(text, expected): assert_parse_number_with_format_d(text, expected) @pytest.mark.parametrize("text, expected", [ ("0o0", 0), ("0o10", 8), ("0o12", 10), ]) def test_parse_number_with_base8(text, expected): assert_parse_number_with_format_d(text, expected) @pytest.mark.parametrize("text, expected", [ ("0x0", 0), ("0x01", 1), ("0x12", 18), ]) def test_parse_number_with_base16(text, expected): assert_parse_number_with_format_d(text, expected) @pytest.mark.parametrize("text1, expected1, text2, expected2", [ ("0x12", 18, "12", 12) ]) def test_parse_number_twice(text1, expected1, text2, expected2): """ENSURE: Issue #121 int_convert memory effect is fixed.""" parser = parse.Parser("{:d}") result1 = parser.parse(text1) result2 = parser.parse(text2) assert result1.fixed[0] == expected1 assert result2.fixed[0] == expected2 parse_type-0.5.6/tests/test_parse_util.py000066400000000000000000000434631372661661400206450ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Test suite to test the :mod:`parse_type.parse_util` module. """ from __future__ import absolute_import, print_function from .parse_type_test import TestCase, unittest from parse_type.parse_util \ import Field, FieldParser, FormatSpec, make_format_spec # ----------------------------------------------------------------------------- # TEST CASE: # ----------------------------------------------------------------------------- class TestField(TestCase): EMPTY_FORMAT_FIELDS = [ Field(), #< Empty field. Field("name"), #< Named field without format. Field("name", ""), #< Named field with format=empty-string. Field(format=""), #< Field with format=empty-string. ] NONEMPTY_FORMAT_FIELDS = [ Field(format="Number"), #< Typed field without name". Field("name", "Number"), #< Named and typed field". ] INVALID_FORMAT_FIELDS = [ Field(format="<"), #< Align without type. Field(format="_<"), #< Fill and align without type. Field(format="_<10"), #< Fill, align and width without type. Field(format="_<098"), #< Fill, align, zero and width without type. ] FIELDS = EMPTY_FORMAT_FIELDS + NONEMPTY_FORMAT_FIELDS + INVALID_FORMAT_FIELDS def test_is_typed__returns_true_for_nonempty_format(self): fields = self.NONEMPTY_FORMAT_FIELDS + self.INVALID_FORMAT_FIELDS for field in fields: self.assertTrue(field.has_format, "Field: %s" % field) def test_is_typed__returns_false_for_empty_format(self): fields = self.EMPTY_FORMAT_FIELDS for field in fields: self.assertFalse(field.has_format, "Field: %s" % field) def test_format_spec__returns_none_if_format_is_empty(self): for field in self.EMPTY_FORMAT_FIELDS: self.assertIsNone(field.format_spec, "Field: %s" % field) def test_format_spec__if_format_is_nonempty_and_valid(self): for field in self.NONEMPTY_FORMAT_FIELDS: self.assertIsNotNone(field.format_spec) self.assertIsInstance(field.format_spec, FormatSpec) def test_format_spec__raises_error_if_nonempty_format_is_invalid(self): for field in self.INVALID_FORMAT_FIELDS: with self.assertRaises(ValueError): field.format_spec def test_format_spec__is_lazy_evaluated(self): fields = [Field(), Field("name"), Field("name", "type"), Field(format="type")] for field in fields: self.assertIsNone(field._format_spec) if field.format: _ = field.format_spec.type self.assertIsNotNone(field.format_spec) else: self.assertIsNone(field.format_spec) def test_set_format_invalidates_format_spec(self): field = Field(format="Number") self.assertEqual(field.format, "Number") self.assertEqual(field.format_spec.type, "Number") self.assertEqual(field.format_spec.align, None) field.set_format("d", "=Number", "^Number+"] for format in formats: format_spec = Field.extract_format_spec(format) expected_align = format[0] expected_type = format[1:] expected_spec = make_format_spec(type=expected_type, align=expected_align) self.assertEqual(format_spec, expected_spec) self.assertValidFormatAlign(format_spec.align) def test_extract_format_spec__with_fill_align_and_type(self): # -- ALIGN_CHARS = "<>=^" formats = ["Xd", "0=Number", " ^Number+"] for format in formats: format_spec = Field.extract_format_spec(format) expected_fill = format[0] expected_align = format[1] expected_type = format[2:] expected_spec = make_format_spec(type=expected_type, align=expected_align, fill=expected_fill) self.assertEqual(format_spec, expected_spec) self.assertValidFormatAlign(format_spec.align) # -- ALIGN_CHARS = "<>=^" FORMAT_AND_FORMAT_SPEC_DATA = [ ("^010Number+", make_format_spec(type="Number+", width="10", zero=True, align="^", fill=None)), ("X<010Number+", make_format_spec(type="Number+", width="10", zero=True, align="<", fill="X")), ("_>0098Number?", make_format_spec(type="Number?", width="098", zero=True, align=">", fill="_")), ("*=129Number*", make_format_spec(type="Number*", width="129", zero=False, align="=", fill="*")), ("X129Number?", make_format_spec(type="X129Number?", width="", zero=False, align=None, fill=None)), (".3Number", make_format_spec(type="Number", width="", zero=False, align=None, fill=None, precision="3")), ("6.2Number", make_format_spec(type="Number", width="6", zero=False, align=None, fill=None, precision="2")), ] def test_extract_format_spec__with_all(self): for format, expected_spec in self.FORMAT_AND_FORMAT_SPEC_DATA: format_spec = Field.extract_format_spec(format) self.assertEqual(format_spec, expected_spec) self.assertValidFormatWidth(format_spec.width) if format_spec.align is not None: self.assertValidFormatAlign(format_spec.align) def test_make_format(self): for expected_format, format_spec in self.FORMAT_AND_FORMAT_SPEC_DATA: format = Field.make_format(format_spec) self.assertEqual(format, expected_format) format_spec2 = Field.extract_format_spec(format) self.assertEqual(format_spec2, format_spec) # ----------------------------------------------------------------------------- # TEST CASE: # ----------------------------------------------------------------------------- class TestFieldParser(TestCase): INVALID_FIELDS = ["", "{", "}", "xxx", "name:type", ":type"] VALID_FIELD_DATA = [ ("{}", Field()), ("{name}", Field("name")), ("{:type}", Field(format="type")), ("{name:type}", Field("name", "type")) ] #def assertFieldEqual(self, actual, expected): # message = "FAILED: %s == %s" % (actual, expected) # self.assertIsInstance(actual, Field) # self.assertIsInstance(expected, Field) # self.assertEqual(actual, expected, message) # # self.assertEqual(actual.name, expected.name, message) # # self.assertEqual(actual.format, expected.format, message) def test_parse__raises_error_with_missing_or_partial_braces(self): for field_text in self.INVALID_FIELDS: with self.assertRaises(ValueError): FieldParser.parse(field_text) def test_parse__with_valid_fields(self): for field_text, expected_field in self.VALID_FIELD_DATA: field = FieldParser.parse(field_text) self.assertEqual(field, expected_field) def test_extract_fields__without_field(self): prefix = "XXX ___" suffix = "XXX {{escaped_field}} {{escaped_field:xxx_type}} XXX" field_texts = [prefix, suffix, prefix + suffix, suffix + prefix] for field_text in field_texts: fields = list(FieldParser.extract_fields(field_text)) self.assertEqual(len(fields), 0) def test_extract_fields__with_one_field(self): prefix = "XXX ___" suffix = "XXX {{escaped_field}} {{escaped_field:xxx_type}} XXX" for field_text, expected_field in self.VALID_FIELD_DATA: fields = list(FieldParser.extract_fields(field_text)) self.assertEqual(len(fields), 1) self.assertSequenceEqual(fields, [expected_field]) field_text2 = prefix + field_text + suffix fields2 = list(FieldParser.extract_fields(field_text2)) self.assertEqual(len(fields2), 1) self.assertSequenceEqual(fields, fields2) def test_extract_fields__with_many_fields(self): MANY_FIELDS_DATA = [ ("{}xxx{name2}", [Field(), Field("name2")]), ("{name1}yyy{:type2}", [Field("name1"), Field(format="type2")]), ("{:type1}xxx{name2}{name3:type3}", [Field(format="type1"), Field("name2"), Field("name3", "type3")]), ] prefix = "XXX ___" suffix = "XXX {{escaped_field}} {{escaped_field:xxx_type}} XXX" for field_text, expected_fields in MANY_FIELDS_DATA: fields = list(FieldParser.extract_fields(field_text)) self.assertEqual(len(fields), len(expected_fields)) self.assertSequenceEqual(fields, expected_fields) field_text2 = prefix + field_text + suffix fields2 = list(FieldParser.extract_fields(field_text2)) self.assertEqual(len(fields2), len(expected_fields)) self.assertSequenceEqual(fields2, expected_fields) def test_extract_types(self): MANY_TYPES_DATA = [ ("{}xxx{name2}", []), ("{name1}yyy{:type2}", ["type2"]), ("{:type1}xxx{name2}{name3:type3}", ["type1", "type3"]), ] for field_text, expected_types in MANY_TYPES_DATA: type_names = list(FieldParser.extract_types(field_text)) self.assertEqual(len(type_names), len(expected_types)) self.assertSequenceEqual(type_names, expected_types) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == '__main__': unittest.main() # Copyright (c) 2012-2013 by Jens Engel (https://github/jenisys/parse_type) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. parse_type-0.5.6/tox.ini000066400000000000000000000045361372661661400152340ustar00rootroot00000000000000# ============================================================================ # TOX CONFIGURATION: parse_type # ============================================================================ # DESCRIPTION: # # Use tox to run tasks (tests, ...) in a clean virtual environment. # Tox is configured by default for online usage. # # Run tox, like: # # tox -e py27 # tox -e py37 # # SEE ALSO: # * https://tox.readthedocs.io/en/latest/config.html # ============================================================================ # -- ONLINE USAGE: # PIP_INDEX_URL = https://pypi.org/simple [tox] minversion = 3.10.0 envlist = py27, py37, py38, pypy, pypy3, doctest skip_missing_interpreters = True sitepackages = False indexserver = default = https://pypi.org/simple # ----------------------------------------------------------------------------- # TEST ENVIRONMENTS: # ----------------------------------------------------------------------------- # install_command = pip install -U {opts} {packages} [testenv] changedir = {toxinidir} commands = pytest {posargs:tests} deps = pytest < 5.0; python_version < '3.0' # >= 4.2 pytest >= 5.0; python_version >= '3.0' pytest-html >= 1.19.0 setenv = TOXRUN = yes PYSETUP_BOOTSTRAP = no [testenv:doctest] commands = pytest --doctest-modules -v parse_type # ----------------------------------------------------------------------------- # MORE TEST ENVIRONMENTS: # ----------------------------------------------------------------------------- [testenv:coverage] commands = pytest --cov=parse_type {posargs:tests} coverage combine coverage html coverage xml deps = {[testenv]deps} pytest-cov coverage>=4.0 [testenv:install] changedir = {envdir} commands = python ../../setup.py install -q {toxinidir}/bin/toxcmd.py copytree ../../tests . pytest {posargs:tests} deps = pytest>=3.2 # ----------------------------------------------------------------------------- # SELDOM USED TEST ENVIRONMENTS: # ----------------------------------------------------------------------------- # -- ENSURE: README.rst is well-formed. # python setup.py --long-description | rst2html.py >output.html ; [testenv:check_setup] ; changedir = {toxinidir} ; commands= ; python setup.py --long-description > output.tmp ; rst2html.py output.tmp output.html ; deps = ; docutils