pax_global_header00006660000000000000000000000064142305450310014507gustar00rootroot0000000000000052 comment=2f52664be5163c1c0844a50c53e4023ed01bd20b doit-0.36.0/000077500000000000000000000000001423054503100125345ustar00rootroot00000000000000doit-0.36.0/.coveragerc000066400000000000000000000000331423054503100146510ustar00rootroot00000000000000[run] source = doit, tests doit-0.36.0/.github/000077500000000000000000000000001423054503100140745ustar00rootroot00000000000000doit-0.36.0/.github/FUNDING.yml000066400000000000000000000004741423054503100157160ustar00rootroot00000000000000# These are supported funding model platforms github: # patreon: # Replace with a single Patreon username open_collective: doit ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel custom: # Replace with a single custom sponsorship URL doit-0.36.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001423054503100162575ustar00rootroot00000000000000doit-0.36.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000006771423054503100207630ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- PLEASE DO NOT USE ISSUE TRACKER TO ASK QUESTIONS: see [contributing](https://github.com/pydoit/doit/blob/master/CONTRIBUTING.md) **Describe the bug** Please include a minimal `dodo.py` that reproduces the problem. If relevant also include the command line used to invoke ``doit``. **Environment** 1. OS: 2. python version: 3. doit version: doit-0.36.0/.github/ISSUE_TEMPLATE/custom.md000066400000000000000000000003761423054503100201210ustar00rootroot00000000000000--- name: Custom issue template about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' --- PLEASE DO NOT USE ISSUE TRACKER TO ASK QUESTIONS: see [contributing](https://github.com/pydoit/doit/blob/master/CONTRIBUTING.md) doit-0.36.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000013551423054503100220100ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- PLEASE DO NOT USE ISSUE TRACKER TO ASK QUESTIONS: see [contributing](https://github.com/pydoit/doit/blob/master/CONTRIBUTING.md) If your question has the form "How can I achieve X with ``doit``?" mostly probably you better start by asking questions on email list... A feature request **SHOULD** have one of: - a clear description of what must be changed in ``doit`` with an example ``dodo.py`` file of how the feature would work. - an example ``dodo.py`` showing a use-case, and ``doit`` limitation **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. doit-0.36.0/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000003231423054503100204460ustar00rootroot00000000000000--- name: Question about: Ask a question title: '' labels: '' assignees: '' --- PLEASE DO NOT USE ISSUE TRACKER TO ASK QUESTIONS: see [contributing](https://github.com/pydoit/doit/blob/master/CONTRIBUTING.md) doit-0.36.0/.github/workflows/000077500000000000000000000000001423054503100161315ustar00rootroot00000000000000doit-0.36.0/.github/workflows/ci.yml000066400000000000000000000034261423054503100172540ustar00rootroot00000000000000name: CI on: push: branches: [master, test] pull_request: branches: [master, test] jobs: test: runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false matrix: os: [ubuntu, windows, macos] python-version: ['3.8', '3.9', '3.10'] # 'pypy-3.8' # pypy broken, see #409 include: # https://github.com/pydoit/doit/issues/372 - os: macos pytest-args: -k 'not(cmd_auto or TestFileWatcher)' # pypy broken, see #409 # - os: macos # python-version: pypy3 # pytest-args: -k 'not(cmd_auto or TestFileWatcher or remove_all or ForgetAll)' exclude: - os: windows python-version: pypy3 steps: - if: ${{ matrix.os == 'ubuntu' }} run: sudo apt-get install strace - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - run: pip install --user -U pip wheel setuptools - id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'dev_requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- - run: pip install . -r dev_requirements.txt - run: pip freeze - run: pip check - run: doit pyflakes - run: doit codestyle - run: py.test -vv ${{ matrix.pytest-args }} - if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.8' }} run: | pip install codecov doit coverage codecov doit-0.36.0/.github/workflows/website.yml000066400000000000000000000011511423054503100203140ustar00rootroot00000000000000name: Website on: push: branches: [master, test] pull_request: branches: [master, test] jobs: build: runs-on: ubuntu-latest steps: - run: sudo apt-get install hunspell hunspell-en-us - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: "3.8" - run: pip install -U pip wheel setuptools - run: pip install . -r doc_requirements.txt -r dev_requirements.txt - run: pip freeze - run: doit -v2 website - uses: actions/upload-artifact@v2 with: name: Website path: ./doc/_build doit-0.36.0/.gitignore000066400000000000000000000002671423054503100145310ustar00rootroot00000000000000*.pyc .doit.db doit.egg-info .coverage .cache dist MANIFEST.in revision.txt tests/data/* doc/_build doc/samples/*.o doc/samples/*.in doc/samples/*.out doc/samples/file* dev .vscode doit-0.36.0/AUTHORS000066400000000000000000000030471423054503100136100ustar00rootroot00000000000000 (in chronological order) * Eduardo Schettino - schettino72 gmail com * Javier Collado - https://launchpad.net/~javier.collado * Philipp Tölke - https://launchpad.net/~toelke+lp * Daniel Hjelm - doit-d hjelm eu * Damiro - https://launchpad.net/~damiro * Charlie Guo - https://launchpad.net/~charlie.guo * Michael Gliwinski - https://launchpad.net/~tzeentch-gm * Vadim Fint - mocksoul gmail com * Thomas Kluyver - https://bitbucket.org/takluyver * Rob Beagrie - http://rob.beagrie.com * Miguel Angel Garcia - http://magmax.org * Roland Puntaier - roland puntaier gmail com * Vincent Férotin - vincent ferotin gmail com * Chris Warrick - Kwpolska - http://chriswarrick.com/ * Ronan Le Gallic - rolegic gmail com * Simon Conseil - contact saimon org * Kostis Anagnostopoulos - ankostis gmail com * Randall Schwager - schwager hsph harvard edu * Pavel Platto - hinidu gmail com * Gerlad Storer - https://github.com/gstorer * Simon Mutch - https://github.com/smutch * Michael Milton - https://github.com/tmiguelt * Mike Pagel - https://github.com/moltob * Marijn van Vliet - https://github.com/wmvanvliet * Niko Wenselowski - https://github.com/okin * Jan Felix Langenbach - o hase3 gmail com * Facundo Ciccioli - facundofc gmail com * Alexandre Allard - https://github.com/alexandre-allard-scality * Trevor Bekolay - https://github.com/tbekolay * Michał Górny - https://github.com/mgorny doit-0.36.0/CHANGES000066400000000000000000000552031423054503100135340ustar00rootroot00000000000000.. meta:: :description: pydoit release history & changelog :keywords: python, doit, release, changelog .. title:: pydoit release history & changelog ======= Changes ======= 0.36.0 (*2022-04-22*) ===================== - BACKWARD INCOMPATIBLE: remove deprecated `doit.cmd_base.py:TaskLoader`. - BACKWARD INCOMPATIBLE: does not use not maintained `toml` package, use `tomli` instead. - BACKWARD INCOMPATIBLE: remove `auto` command, use plugin `doit-auto1`. - BACKWARD INCOMPATIBLE: plugins use importlib-metadata instead of setuptools. - BACKWARD INCOMPATIBLE: remove extra install `doit[plugins]`. - BACKWARD INCOMPATIBLE: `UnmetDependency`, `SetupError` and `DependencyError` as sub-class of `TaskError`. - DEPRECATED: `CatchedException`, renamed to `BaseFail`. - api.run_tasks(): support `pos_arg`. - add reporter `error-only` - add attribute `report` to `BaseFail`. Controls if reporter should create own output for failures. If `False` relies on underlying action to output failure information. 0.35.0 (*2022-04-07*) ===================== - BACKWARD INCOMPATIBLE: Drop Python 3.6 support. - BACKWARD INCOMPATIBLE: Drop Python 3.7 support. - BACKWARD INCOMPATIBLE: `create_doit_tasks` does NOT automatically ignored when processing class. - Fix #344: Command `clean`: add support for wildcard `*`. - Add `TaskLoader2.task_opts` attribute. This is passed to task creator and also as task config. - CmdOption add `metavar` attribute. - Fix #156: `actions.py:Writer`, add support for `fileno()`. Taken from original stream. - `create_doit_tasks` supports `basename` attribute. - Task: add attribute `io`; add `io.capture`. - add `api.py::run_tasks()` 0.34.2 (*2022-02-18*) ===================== - Fix #416: compare `task_params` using `is` operator. 0.34.1 (*2022-01-12*) ===================== - Fix #410: if TOML lib not present, just print warning instead of raising error. 0.34.0 (*2021-12-11*) ===================== - BACKWARD INCOMPATIBLE: Drop Python 3.5 support - python 3.9 support - python 3.10 support - Fix #381: Allow Delayed tasks to be executed multiple times in same process. - Fix #382: TaskResult make sure `started` include microsecond as decimal number. - Fix #387: Pass pickled attributes, err and out when a task fails in multi processes context. - Fix #398: Add `setuptools` as` extras_require`. - Fix #373: read DOIT_CONFIG from TOML. - Fix #405: Add Task attribute `meta`. - Fix #349: Handle passing task args in "single" task execution. - Fix #377: cmd forget, added options `--disable-default` and `--all`. - Fix #307: Allows `create_after` decorator to be used on methods. - Fix #311: Add `@task_params` decorator. 0.33.1 (*2020-09-04*) ===================== - fix sdist distribution to include all files 0.33.0 (*2020-09-01*) ===================== - python 3.8 support - BACKWARD INCOMPATIBLE: Drop Python 3.4 support - fix error installing with setuptools 50.0 0.32.0 (*2019-12-10*) ===================== - python 3.7 support - BACKWARD INCOMPATIBLE: Changed signature of `DoitCommand` - BACKWARD INCOMPATIBLE: Fix #254: Python actions keywords with task metadata `dependencies`, `targets` and `changed` are a copied list - BACKWARD INCOMPATIBLE: Custom backend initializer added parameter ``codec_cls`` - Fix #204: Accept loader command options before command name - Fix #240: Command run: with `--pdb` drop into *pdb* on unhandled exception on `PythonAction` - Fix #203: Allow configuration of `dodo.py` file name with env var `DOIT_FILE` - Fix #113: `tools.config_changed` deals with nested dictionaries. Using json instead of repr. - Fix #261: help organize command options in sections, and improve formatting. - Fix #267: `doit list` now has a `--sort` parameter to determine the order in which the tasks are listed. - Make it possible to use a custom encoder when using config_changed with a dict. - Add configuration `DOIT_CONFIG` `action_string_formatting` to control command action formatter. - Fix `result_dep`, use result **after** its execution - Fix #286: Support `functools.partial` on tasks' dict metadata `task.title` - Fix #285: `clean` command, remove targets in reverse lexical order - Deprecated `TaskLoader` in favor of `TaskLoader2`, which separates loading doit's configuration from loading tasks. - Fix #288: Added `doit.globals.Globals.dep_manager` to access dependency manager during all task processing phases. - Fix #98: Support custom backend codecs so that python-action return values which are not JSON serializable may be used. - Fix #283: set task options from doit.cfg 0.31.1 (*2018-03-18*) ===================== - Fix #249 reporter bug when using `--continue` option - Fix #248 test failures of debian when using GDBM - Fix #164 `get_var` fails on multiprocess execution on Windows - Fix #245 custom `clean` action takes `dry-run` into account 0.31.0 (*2018-02-25*) ===================== - BACKWARD INCOMPATIBLE: Drop Python 3.3 support - Fix #171 Passing environment variables to CmdAction - Fix #184 parametrize script name - CmdParse now support getting values from OS environment variables - option `seek_file` control by ENV var `DOIT_SEEK_FILE` - #192 ipython extension uses `load_ipython_extension` - #218 clean with option `--forget` can be used to also forget about cleaned tasks - Fix strace command (seems strace output was modified) - Fix #224: use `mock` from stdlib - #227: enhancements to `info` command - Fix #197: improve error message for invalid `clean` and `teardown` task params - Fix #211: do not display traceback for error when missing `file_dep` - Task `verbosity` has precedence over `verbosity` from config - Fix #140: add `failure-verbosity`. ConsoleReporter, by default, do not print stderr twice. - Fix #155: pass `selected_tasks` to `Reporter.initialize()` - Fix #221: do not leak meta arguments to actions `kwargs` - Fix #202: help command display option's name used on config - Fix #209: fix `clean` ordering, and following implicit task_deps - Fix: list of sub-tasks do not include non-related `task_dep` - Internal: Removed `Task.is_subtask` use `Task.subtask_of` instead 0.30.3 (*2017-02-20*) ===================== - Revert usage of setuptools environment markers (feature too new) 0.30.2 (*2017-02-16*) ===================== - Fix dependency on `pathlib` from PyPi 0.30.1 (*2017-02-16*) ===================== - Fix GH-#159 KeyError on doit list --status when missing file dependency - add python3.6 support 0.30.0 (*2016-11-22*) ===================== - BACKWARD INCOMPATIBLE: #112 drop python2 compatibility - GH-#94: option to read output from CmdAction line or byte buffered - GH-#114: `file_dep`, `targets` and `CmdAction` support pathlib. - fix GH-#100: make cmd `completion` output deterministic - fix GH-#99: positional argument on tasks not specified from cmd-line - fix GH-#97: `list` command does not display task-doc for `DelayedTask` when `creates` is specified - fix GH-#131: race condition in doit.tools.create_folder - fix `auto` command on OS-X systems - fix GH-#117: Give error when user tries to use equal sign on task name 0.29.0 (*2015-08-16*) ===================== - BACKWARD INCOMPATIBLE: revert - `result_dep` to create an implicit `task_dep` - fix GH-#59: command `list` issue with unicode names - fix GH-#72: cmd `completion` escaping of apostrophes in zsh - fix GH-#74: Task action's handle python3 callables with keyword only args - fix GH-#50: Executing tasks in parallel (multi-process) fails on Windows - fix GH-#71 #92: Better error messages for invalid command line tasks/commands - fix issue with `--always-execute` and `setup` tasks - GH-#67: multiprocess runner handles closures in tasks (using cloudpickle) - GH-#58: add `DelayedLoader` parameter `target_regex` - GH-#30: add `DelayedLoader` parameter `creates` - GH-#58: cmd `Run` add option `--auto-delayed-regex` - GH-#24: cmd `info` add option `--status` show reason a task is not up-to-date - GH-#66: cmd `auto` support custom ( user specified ) commands to be executed after each task execution - GH-#61: speed up sqlite3 backend (use internal memory cache) 0.28.0 (*2015-04-22*) ===================== - BACKWARD INCOMPATIBLE: signature for custom DB backend changed - BACKWARD INCOMPATIBLE: `DoitMain` API change - BACKWARD INCOMPATIBLE: `Command` API change - BACKWARD INCOMPATIBLE: `default` reporter renamed to `console` - GH-#25: Add a `reset-dep` command to recompute dependencies state - GH-#22: Allow to customize how file_dep are checked - GH-#31: Add IPython `%doit` magic-function loading tasks from its global namespace - read configuration options from INI files - GH-#32 plugin system - plugin support: COMMAND - add new commands - plugin support: LOADER - add custom task loaders - plugin support: REPORTER - add custom reporter for `run` command - plugin support: BACKEND - add custom DB persistence backend - GH-#36 PythonAction recognizes returned TaskError or TaskFailed - GH-#37 CmdParse support for arguments of type list - GH-#47 CmdParse support for choices - fix issue when using unicode strings to specify `minversion` on python 2 - fix GH-#27 auto command in conjunction with task arguments - fix GH-#44 Fix the list -s command when result_dep is used - fix GH-#45 make sure all `uptodate` checks are executed (no short-circuit) 0.27.0 (*2015-01-30*) ====================== - BACKWARD INCOMPATIBLE: drop python 2.6 support - BACKWARD INCOMPATIBLE: removed unmaintained genstandalone script - BACKWARD INCOMPATIBLE: removed runtests.py script and support to run tests through setup.py - BACKWARD INCOMPATIBLE: `result_dep` creates an implicit `setup` (was `task_dep`) - BACKWARD INCOMPATIBLE: GH-#9 `getargs` creates an implicit `result_dep` - BACKWARD INCOMPATIBLE: `CmdAction` would always decode process output using `errors='strict'` default changed to `replace` - allow task-creators to return/yield Task instances - fix GH-#14: add support for delayed task creation - fix GH-#15: `auto` (linux) inotify also listen for `MOVE_TO` events - GH-#4 `CmdAction` added parameters `encoding` and `decode_error` - GH-#6: `loader.task_loader()` accepts methods as *task creators* 0.26.0 (*2014-08-30*) ====================== - moved development to git/github - `uptodate` callable "magic" arguments `task` and `values` are now optional - added command `info` to display task metadata - command `clean` smarter execution order - remove `strace` short option `-k` because it conflicts with `run` option - fix zsh tab-completion script when not `doit` script - fix #79. Use setuptools and `entry_points` - order of yielded tasks is preserved - #68. pass positional args to tasks - fix tab-completion on BASH for sub-commands that take file arguments 0.25.0 (*2014-03-26*) ====================== - BACKWARD INCOMPATIBLE: use function `doit.get_initial_workdir()` instead of variable `doit.initial_workdir` - DEPRECATED `tools.InteractiveAction` renamed to `tools.LongRunning` - fix: `strace` raises `InvalidCommand` instead of using `assert` - #28: task `uptodate` support string to be executed as shell command - added `tools.Interactive` for use with interactive commands - #69: added doit.run() to make it easier to turn a dodo file into executable - #70: added option "--pdb" to command `run` - added option "--single" to command `run` - include list of file_dep as an implicit dependency 0.24.0 (*2013-11-24*) ====================== - reporter added `initialize()` - cmd `list`: added option `--template` - dodo.py can specify minimum required doit version with DOIT_CONFIG['minversion'] - #62: added the absolute path from which doit is invoked `doit.initial_workdir` - fix #36: added method `isatty()` to `action.Writer` - added command `tabcompletion` for bash and zsh - fix #56: allow python actions to have default values for task parameters 0.23.0 (*2013-09-20*) ====================== - support definition of group tasks using basename without any task - added task property `watch` to specific extra files/folders in auto command - CmdAction support for all arguments of subprocess.Popen, but stdout and stderr - added command option `-k` as short for `--seek-file` - task action can be specified as a list of strings (executed using subprocess.Popen shell=False) - fix #60: result of calc_dep only considered if not run yet - fix #61: test failures involving DBM - fix: do not allow duplicate task names 0.22.1 (*2013-08-04*) ====================== - fix reporter output in py3 was being displayed as bytes instead of string - fix pr#12 read file in chunks when calculating MD5 - fix #54 - remove distribute bootstrapping during installation 0.22.0 (*2013-07-05*) ====================== - fix #49: skip unicode tests on systems with non utf8 locale - fix #51: bash completion does not mess up with global COMP_WORDBREAKS - fix docs spelling and added task to check spelling - fix #47: Task.options can always be accessed from `uptodate` code - fix #45: cmd forget, added option -s/--follow-sub to forget task_dep too 0.21.1 (*2013-05-21*) ====================== - fix tests on python3.3.1 - fix race condition on CmdAction (affected only python>=3.3.1) 0.21.0 (*2013-04-29*) ====================== - fix #38: `doit.tools.create_folder()` raise error if file exists in path - `create_doit_tasks` not called for unbound methods - support execution using "python -m doit" - fix #33: Failing to clean a group of task(s) with sub-tasks - python-actions can take a magic "task" parameter as reference to task - expose task.clean_targets - tools.PythonInteractiveAction saves "result" and "values" - fix #40. added option to use threads for parallel running of tasks - same code base for python 2 & 3 (no need to use tool `2to3`) - add sqlite3 DB backend - added option to select backend 0.20.0 (*2013-01-09*) ====================== - added command `dumpdb` - added `CmdAction.save_out` param - `CmdAction` support for callable that returns a command string - BACKWARD INCOMPATIBLE `getargs` for a group task gets a dict where each key is the name of subtasks (previously it was a list) - added command `strace` - cmd `auto` run tasks on separate process - support unicode for task name 0.19.0 (*2012-12-18*) ====================== - support for `doit help ` - added support to load tasks using `create_doit_tasks` - dropped python 2.5 support 0.18.1 (*2012-12-03*) ======================= - fix bug cmd option --continue not being recognized 0.18.0 (*2012-11-27*) ======================= - remove DEPRECATED `Task.insert_action`, `result_dep` and `getargs` using strings - fix #10 --continue does not execute tasks that have failed dependencies - fix --always-execute does not execute "ignored" tasks - fix #29 python3 cmd-actions issue - fix #30 tests pass on all dbm backends - API to add new sub-commands to doit - API to modify task loader - API to make dodo.py executable - added ZeroReporter 0.17.0 (*2012-09-20*) ====================== - fix #12 Action.out and Action.err not set when using multiprocessing - fix #16 fix `forget` command on gdbm backend - fix #14 improve parallel execution (better process utilization) - fix #9 calc_dep create implicit task_dep if a file_dep returned is a also a target - added tools.result_dep - fix #15 tools.result_dep supports group-tasks - DEPRECATE task attribute `result_dep` (use tools.result_dep) - DEPRECATE `getargs` specification using strings (must use 2-element tuple) - several changes on `uptodate` - DEPRECATE `Task.insert_action` (replaced by `Task.value_savers`) - fix #8 `clean` cleans all subtasks from a group-task - fix #8 `clean` added flag `--all` to clean all tasks - fix #8 `clean` when no task is specified set --clean-dep and clean default tasks 0.16.1 (*2012-05-13*) ====================== - fix multiprocessing/parallel bug - fix unicode bug on tools.config_changed - convert tools uptodate stuff to a class, so it can be used with multi-processing 0.16.0 (*2012-04-23*) ======================= - added task parameter ``basename`` - added support for task generators yield nested python generators - ``doit`` process return value ``3`` in case tasks do start executing (reporter is not used) - task parameter ``getargs`` take a tuple with 2 values (task_id, key_name) - DEPRECATE ``getargs`` being specified as . - ``getargs`` can take all values from task if specified as (task_id, None) - ``getargs`` will pass values from all sub-tasks if specified task is a group task - result_dep on PythonAction support checking for dict values - added ``doit.tools.PythonInteractiveAction`` 0.15.0 (*2012-01-10*) ======================= - added option --db-file (#909520) - added option --no-continue (#586651) - added genstandalone.py to create a standalone ``doit`` script (#891935) - fix doit.tools.set_trace to not modify sys.stdout 0.14.0 (*2011-11-05*) ======================== - added tools.InteractiveAction (#865290) - bash completion script - sub-command list: tasks on alphabetical order, better formatting (#872829) - fix ``uptodate`` to accept instance methods callables (#871967) - added command line option ``--seek-file`` - added ``tools.check_unchanged_timestamp`` (#862606) - fix bug subclasses of BaseAction should get a task reference 0.13.0 (*2011-07-18*) ======================== - performance speed improvements - fix bug on unicode output when task fails - ConsoleReporter does not output task's title for successful tasks that start with an ``_`` - added ``tools.config_changed`` (to be used with ``uptodate``) - ``teardown`` actions are executed in reverse order they were registered - added ``doit.get_var`` to get variables passed from command line - getargs creates implicit "setup" task not a "task_dep" 0.12.0 (*2011-05-29*) ======================= - fix bug #770150 - error on task dependency from target - fix bug #773579 - unicode output problems - task parameter ``uptodate`` accepts callables - deprecate task attribute run_once. use tools.run_once on uptodate instead - added doit.tools.timeout 0.11.0 (*2011-04-20*) ======================== - no more support for python2.4 - support for python 3.2 - fix bug on unicode filenames & unicode output (#737904) - fix bug when using getargs together with multiprocess (#742953) - fix for dumbdbm backend - fix task execution order when using "auto" command - fix getargs when used with sub-tasks - fix calc_dep when used with "auto" command - "auto" command now support verbosity control option 0.10.0 (*2011-01-24*) ====================== - add task parameter "uptodate" - add task parameter "run_once" - deprecate file_dep bool values and None - fix issues with error reporting for JSON Reporter - "Reporter" API changes - ".doit.db" now uses a DBM file format by default (speed optimization) 0.9.0 (*2010-06-08*) ===================== - support for dynamic calculated dependencies "calc_dep" - support for user defined reporters - support "auto" command on mac - fix installer on mac. installer aware of different python versions - deprecate 'dependencies'. use file_dep, task_dep, result_dep. 0.8.0 (*2010-05-16*) ======================= - parallel execution of tasks (multi-process support) - sub-command "list" option "--deps", show list of file dependencies - select task by wildcard (fnmatch) i.e. test:folderXXX/* - task-setup can be another task - task property "teardown" substitute of setup-objects cleanup - deprecate setup-objects 0.7.0 (*2010-04-08*) ===================== - configure options on dodo file (deprecate DEFAULT_TASKS)(#524387) - clean and forget act only on default tasks (not all tasks) (#444243) - sub-command "clean" option "clean-dep" to follow dependencies (#444247) - task dependency "False" means never up-to-date, "None" ignored - sub-command "list" by default do not show tasks starting with an underscore, added option (-p/--private) - new sub-command "auto" 0.6.0 (*2010-01-25*) ===================== - improve (speed optimization) of check if file modified (#370920) - sub-command "clean" dry-run option (-n/--dry-run) (#444246) - sub-command "clean" has a more verbose output (#444245) - sub-command "list" option to show task status (-s/--status) (#497661) - sub-command "list" filter tasks passed as positional parameters - tools.set_trace, PDB with stdout redirection (#494903) - accept command line optional parameters passed before sub-command (#494901) - give a clear error message if .doit.db file is corrupted (#500269) - added task option "getargs". actions can use computed values from other tasks (#486569) - python-action might return a dictionary on success 0.5.1 (*2009-12-03*) ===================== - fix. task-result-dependencies should be also added as task-dependency to force its execution. 0.5.0 (*2009-11-30*) ===================== - task parameter 'clean' == True, cleans empty folders, and display warning for non-empty folders - added command line option --continue. Execute all tasks even if tasks fails - added command line option --reporter to select result output reporter - added executed-only reporter - added json reporter - support for task-result dependency #438174 - added sub-command ignore task - added command line option --outfile. write output to specified file path - added support for passing arguments to tasks on cmd line - added command line option --dir (-d) to set current working directory - removed dodo-sample sub-command - added task field 'verbosity' - added task field 'title' - modified default way a task is printed on console (just show ". name"), old way added to doit.tools.task_title_with_actions 0.4.0 (*2009-10-05*) ==================== - deprecate anything other than a boolean values as return of python actions - sub-cmd clean (#421450) - remove support for task generators returning action (not documented behavior) - setup parameter for a task should be a list - single value deprecated (#437225) - PythonAction support 'dependencies', 'targets', 'changed' parameters - added tools.create_folder (#421453) - deprecate folder-dependency - CmdActions reference to dependencies, targets and changed dependencies (#434327) - print task description when printing through doit list (#425811) - action as list of commands/python (#421445) - deprecate "action" use "actions" 0.3.0 (*2009-08-30*) ===================== - added subcommand "forget" to clear successful runs status (#370911) - save run results in text file using JSON. (removed dbm) - added support for DEFAULT_TASKS in dodo file - targets md5 is not checked anymore. if target exist, task is up-to-date. it also supports folders - cmd line sub-commands (#370909) - remove hashlib dependency on python 2.4 - sub-cmd to create dodo template - cmd-task supports a list of shell commands - setup/cleanup for task (#370905) 0.2.0 (*2009-04-16*) ==================== - docs generated using sphinx - execute once (dependency = True) - group task - support python 2.4 and 2.6 - folder dependency 0.1.0 (*2008-04-14*) ==================== - initial release doit-0.36.0/CONTRIBUTING.md000066400000000000000000000022151423054503100147650ustar00rootroot00000000000000 # Contributing to doit ## issues/bugs If you find issues using `doit` please report at [github issues](https://github.com/pydoit/doit/issues). All issues should contain a sample minimal `dodo.py` and the used command line to reproduce the problem. ## questions Please ask question in the discussion [forum](http://groups.google.co.in/group/python-doit) or on StackOverflow using tag `doit`. Do not use the github issue tracker to ask questions! `doit` has extensive online documentation please read the docs! When asking a question it is appreciated if you introduce yourself, also mention how long are you using doit and using it for what. A good question with a code example greatly increases the chance of it getting a reply. Unless you are looking for paid support, do **not** send private emails to the project maintainer. ## feature request Users are expected to implement new features themselves. You are welcome to add a request on github tracker but if you are not willing to spend your time on it, probably nobody else will... Before you start implementing anything it is a good idea to discuss its implementation in the discussion forum. doit-0.36.0/LICENSE000066400000000000000000000021071423054503100135410ustar00rootroot00000000000000 The MIT License Copyright (c) 2008-present Eduardo Naufel Schettino 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. doit-0.36.0/README.rst000066400000000000000000000143401423054503100142250ustar00rootroot00000000000000================ README ================ .. display some badges .. image:: https://img.shields.io/pypi/v/doit.svg :target: https://pypi.python.org/pypi/doit .. image:: https://github.com/pydoit/doit/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/pydoit/doit/actions/workflows/ci.yml?query=branch%3Amaster .. image:: https://codecov.io/gh/pydoit/doit/branch/master/graph/badge.svg?token=wxKa1h11zn :target: https://codecov.io/gh/pydoit/doit .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4892136.svg :target: https://doi.org/10.5281/zenodo.4892136 Financial contributions on `Open Collective `_ doit - automation tool ====================== *doit* comes from the idea of bringing the power of build-tools to execute any kind of task *doit* can be uses as a simple **Task Runner** allowing you to easily define ad hoc tasks, helping you to organize all your project related tasks in an unified easy-to-use & discoverable way. *doit* scales-up with an efficient execution model like a **build-tool**. *doit* creates a DAG (direct acyclic graph) and is able to cache task results. It ensures that only required tasks will be executed and in the correct order (aka incremental-builds). The *up-to-date* check to cache task results is not restricted to looking for file modification on dependencies. Nor it requires "target" files. So it is also suitable to handle **workflows** not handled by traditional build-tools. Tasks' dependencies and creation can be done dynamically during it is execution making it suitable to drive complex workflows and **pipelines**. *doit* is build with a plugin architecture allowing extensible commands, custom output, storage backend and "task loader". It also provides an API allowing users to create new applications/tools leveraging *doit* functionality like a framework. *doit* is a mature project being actively developed for more than 10 years. It includes several extras like: parallel execution, auto execution (watch for file changes), shell tab-completion, DAG visualisation, IPython integration, and more. Sample Code =========== Define functions returning python dict with task's meta-data. Snippet from `tutorial `_: .. code:: python def task_imports(): """find imports from a python module""" for name, module in PKG_MODULES.by_name.items(): yield { 'name': name, 'file_dep': [module.path], 'actions': [(get_imports, (PKG_MODULES, module.path))], } def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'targets': ['requests.dot'], 'actions': [module_to_dot], 'getargs': {'imports': ('imports', 'modules')}, 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.dot'], 'targets': ['requests.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } Run from terminal:: $ doit list dot generate a graphviz's dot graph from module imports draw generate image from a dot file imports find imports from a python module $ doit . imports:requests.models . imports:requests.__init__ . imports:requests.help (...) . dot . draw Project Details =============== - Website & docs - http://pydoit.org - Project management on github - https://github.com/pydoit/doit - Discussion group - https://groups.google.com/forum/#!forum/python-doit - News/twitter - https://twitter.com/pydoit - Plugins, extensions and projects based on doit - https://github.com/pydoit/doit/wiki/powered-by-doit license ======= The MIT License Copyright (c) 2008-2021 Eduardo Naufel Schettino see LICENSE file developers / contributors ========================== see AUTHORS file install ======= *doit* is tested on python 3.6 to 3.10. The last version supporting python 2 is version 0.29. .. code:: bash $ pip install doit dependencies ============= - cloudpickle - pyinotify (linux) - macfsevents (mac) Tools required for development: - git * VCS - py.test * unit-tests - coverage * code coverage - sphinx * doc tool - pyflakes * syntax checker - doit-py * helper to run dev tasks development setup ================== The best way to setup an environment to develop *doit* itself is to create a virtualenv... .. code:: bash doit$ virtualenv dev doit$ source dev/bin/activate install ``doit`` as "editable", and add development dependencies from `dev_requirements.txt`: .. code:: bash (dev) doit$ pip install --editable . (dev) doit$ pip install --requirement dev_requirements.txt tests ======= Use py.test - http://pytest.org .. code:: bash $ py.test documentation ============= ``doc`` folder contains ReST documentation based on Sphinx. .. code:: bash doc$ make html They are the base for creating the website. The only difference is that the website includes analytics tracking. To create it (after installing *doit*): .. code:: bash $ doit website spell checking -------------- All documentation is spell checked using the task `spell`: .. code:: bash $ doit spell It is a bit annoying that code snippets and names always fails the check, these words must be added into the file `doc/dictionary.txt`. The spell checker currently uses `hunspell`, to install it on debian based systems install the hunspell package: `apt-get install hunspell`. profiling --------- .. code:: bash python -m cProfile -o output.pstats `which doit` list gprof2dot -f pstats output.pstats | dot -Tpng -o output.png releases ======== Update version number at: - doit/version.py - setup.py - doc/conf.py - doc/index.html .. code:: bash python setup.py sdist python setup.py bdist_wheel twine upload dist/doit-X.Y.Z.tar.gz twine upload dist/doit-X.Y.Z-py3-none-any.whl Remember to push GIT tags:: git push --tags contributing ============== On github create pull requests using a named feature branch. Financial contribution to support maintenance welcome. .. image:: https://opencollective.com/doit/tiers/backers.svg?avatarHeight=50 :target: https://opencollective.com/doit/tiers doit-0.36.0/TODO.txt000066400000000000000000000006751423054503100140520ustar00rootroot00000000000000 see https://github.com/pydoit/doit/issues 0.X ---------- . setup/task single process/all procces . better terminal output (#5) wishlist ---------- . tools - profile . tools - code coverage . color output on the terminal . option dont save successful results . forget a dependency, not a task . task name alias . action to be executed on when ctrl-c is hit on auto mode big refactorings ------------------ . Task into TaskDep + Task doit-0.36.0/bash_completion_doit000066400000000000000000000074451423054503100166560ustar00rootroot00000000000000# bash completion for doit # auto-generate by `doit tabcompletion` # to activate it you need to 'source' the generate script # $ source # reference => http://www.debian-administration.org/articles/317 # patch => http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=711879 _doit() { local cur prev words cword basetask sub_cmds tasks i dodof COMPREPLY=() # contains list of words with suitable completion # remove colon from word separator list because doit uses colon on task names _get_comp_words_by_ref -n : cur prev words cword # list of sub-commands sub_cmds="auto clean dumpdb forget help ignore info list reset-dep run strace tabcompletion" # options that take file/dir as values should complete file-system if [[ "$prev" == "-f" || "$prev" == "-d" || "$prev" == "-o" ]]; then _filedir return 0 fi if [[ "$cur" == *=* ]]; then prev=${cur/=*/} cur=${cur/*=/} if [[ "$prev" == "--file=" || "$prev" == "--dir=" || "$prev" == "--output-file=" ]]; then _filedir -o nospace return 0 fi fi # get name of the dodo file for (( i=0; i < ${#words[@]}; i++)); do case "${words[i]}" in -f) dodof=${words[i+1]} break ;; --file=*) dodof=${words[i]/*=/} break ;; esac done # dodo file not specified, use default if [ ! $dodof ] then dodof="dodo.py" fi # get task list # if it there is colon it is getting a subtask, complete only subtask names if [[ "$cur" == *:* ]]; then # extract base task name (remove everything after colon) basetask=${cur%:*} # sub-tasks tasks=$(doit list --file="$dodof" --quiet --all ${basetask} 2>/dev/null) COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) ) __ltrim_colon_completions "$cur" return 0 # without colons get only top tasks else tasks=$(doit list --file="$dodof" --quiet 2>/dev/null) fi # match for first parameter must be sub-command or task # FIXME doit accepts options "-" in the first parameter but we ignore this case if [[ ${cword} == 1 ]] ; then COMPREPLY=( $(compgen -W "${sub_cmds} ${tasks}" -- ${cur}) ) return 0 fi case ${words[1]} in auto) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; clean) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; dumpdb) COMPREPLY=( $(compgen -f -- $cur) ) return 0 ;; forget) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; help) COMPREPLY=( $(compgen -W "${tasks} ${sub_cmds}" -- $cur) ) return 0 ;; ignore) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; info) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; list) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; reset-dep) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; run) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; strace) COMPREPLY=( $(compgen -W "${tasks}" -- $cur) ) return 0 ;; tabcompletion) COMPREPLY=( $(compgen -f -- $cur) ) return 0 ;; esac # if there is already one parameter match only tasks (no commands) COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) ) } complete -o filenames -F _doit doit doit-0.36.0/dev_requirements.txt000066400000000000000000000003221423054503100166530ustar00rootroot00000000000000# modules required for development only # $ pip install --requirement dev_requirements.txt setuptools # for plugins pyflakes pycodestyle pytest>=7.1.0 coverage>=6.0 doit-py>=0.4.0 tomli; python_version<"3.11" doit-0.36.0/doc/000077500000000000000000000000001423054503100133015ustar00rootroot00000000000000doit-0.36.0/doc/Makefile000066400000000000000000000011461423054503100147430ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -v # debug SPHINXBUILD = sphinx-build SPHINXPROJ = doit SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) doit-0.36.0/doc/_static/000077500000000000000000000000001423054503100147275ustar00rootroot00000000000000doit-0.36.0/doc/_static/doit-logo-small.png000066400000000000000000000123611423054503100204430ustar00rootroot00000000000000PNG  IHDR2rsBIT|d pHYsvv`OtEXtSoftwarewww.inkscape.org<nIDATx\yxՑY$K4Y#8/l qL&&>nKXBC8al_ai$[dYt?fz<# ~Uի^Uj(F1QbEji>I-?h Vx<(tV)-D(>A0eˢ8r[D"|mIa,nkۜQ|2bΖ!kTYȧ( TBW*<àmlD~:?|)1F񃈚L4![Z@mpo?f{y( !Npk/j*4̼lyҮʕ_s_zŊRPH4=OJFG/+(^^ʹkxZTUUQDR;e˖H.2A emiÔ:ܫ>z=zx(ggq8~b["̡GUT*f>@N)姛 r5KGKDoiِ3U״#gc0؞'Qi 1 JypUC%%<\)px`leh6&y -$4&Dz;h;Kn˵1ץFuuK5\ oN2E!zq_K\iZ+su}S]]=t)ac߿->Q|r}m~$d~k뫹*cb@{^};x'iG w Rl>5}>ᖟ_MD_jliyrP-MMnhiOFl+SdwO3ǎ=c<0%L6MW5b@yRQ.v\߿h- z{L(Qm$D E mmC/ $EpO;[0o*U\eh%HAr>]O) 2u+{]LY دpor*uDIPCG ~rľ^pV*<9T5 m?ح`fDiLxDY0& =C૪;Dmhi+i^e |'E;A]vS'תbiO4ǮִgNR4#*%۶jy}utnDV@G ǧTLUg2 Uur)ONeaA ~5 <DT-㩧jK'Uc "ir?5ND62 Q qn`>kbI&%O].ו w .( \XXTq^j9c,ҙID4775E:\dM4 }O qz ~ SN";)pLʣ&5o Z"HxjJ!r(_l6ۃ@`G)oY|YIN!}G$1K|»tӧi] !yHduyr'!ya*Qr! ͶnL2Ra4 @QVFѠ,LD{r7[KJEDep1YYɨ0sKg?@u\gBL Njg\} [vHUo)⌼~&0Y$`{0xy.e?b d" P"&d4Zl(KH>Vv']8Zͦysmm|#+D‚4 s/)ʠ!-b|"$Db i;bNk:X z<>OqO eCd ުܓط]@T}{{n+++t^```"!p*O-ۃ]O8!$뭀˯;:ӿd!ÇgA>1QӞp++lD`zAt% ^ѠEļ@#0ѷjjjag&JR(NE%sr==ҢkƌydO5mndl{V)vՉ2E" (s#{4DwD%nJ:R۝Pn-_CK%}--܌2@3 -JW\||eO2P,T&2lq0Ƿ%yykJz2SҶhhBE Q)GM(b~VOq`EJy$ /38x`zqAN1l+Ei 2FlIFq499H}B(Xɗ\+n)?"S::zmbVƧtI0 Y%AJk|O,]nП(}@RdYObnR6(Fs86sU$8FfRdS3v 6"e13Ό9yʡUlxc!'Aduf[=<6} X3!fV:{Zl!+mQBT[/etDSǥܞJ(TM3`2W&ҙAC8K"r0jMļe("xVyog&Ac'wueY  0(fYs:EmmcKP>඲B.aAo @ a (IZgR8Af}ikFӃMh&Gek{^y%m*QsTnM}#DE (pv, LpiI_[׉[)ex=FQfR;$*/9eI?!GrgNWx (~k:?<+Ü1d{|j>طGʇ-ĭ(߽|,L \)嫝RuO8rAkk:;$5! 0u@m+tpMr㐥m *Dε$oGEч(^!dgXE X nbn"!VX(l )7jlZ}3Z[immv/,E( p8O!\=R&FdޑpQۯEl giH-y@uDd&ה0IJW2mٸ[b׺\Ip@^^!Rͷ;VN厎p*wf׻ldXYg!wޞ?B_S=B/ą8MME}{"k !Z D -:2zLSܒW\LJYD͖݌45KXYwm`~w353P$劒"zQi}8yA8ܺ5Y:}6[`[A IhV^oحFCU+E7؋7j5L<.@Է;|>(q0bp1`ze\ 8Y%r2Q!1%jzCZ3K(F1QbOt0ޏߒIENDB`doit-0.36.0/doc/_static/doit-logo.png000066400000000000000000000213101423054503100173270ustar00rootroot00000000000000PNG  IHDR2rsBIT|d pHYsvv`OtEXtSoftwarewww.inkscape.org< IDATxyU{,e2Kw0I:!BLM l"(SAٔE( @2$5!,]qfzzzdB7]nݺ]uν{ <<<<<<<<<<<<<<<<<<<<<<<!cpJ4Oxxx^ J/UT_>vl͎jǎEC  w[<<Ϳq N5:8H69 N_?O!Z̴>zucs;oҚ4[UuDyovwЖfwWV.ra^Tӛom7c. l"̚P(T;:T%d'JCJI@<<~4ahoN8KN`γ꺐].7ua˳X[[;=Asַ*f)Z_f^zR.`{4o@Xl`_+?~>sxxx|H@ڂLs@},vۤHk-*-#{wCls,]eTY]Նq~pНqiފ颿:qB(e=>0F55(Ujkk|:`Ub %W54|=M4KL[OR|.^$dY-@V r95i8 IESJu< @[ llm۾ٰ!P 0X|| 6O8ŒFo bChy˜6htU--ֱ +j5*>,[K(WZ5J)`ihZ'+ݛ֡B<^1yIZ_My)UwA}&~|sXZb.*N"emuuuu[q:C)U}ꬫۼ*8|F)@߂r.,4Qnhj[E޲^k3;HGǞkoe-f u,U`+\S Ch]l(NkՍ ^B8X]Oh=,kPTT*c>> &!jec^ ]XVT6_KTLbWyDpw*/Ac7ᲲOioo5D/kw)5r.j`Z%~aPe|+*UAh}.='V" >m[|`So3t #\`܌F#~$C/>x dw1Dև>&+cZ2kҭںꑭ[ݨR^X ۾w->yZz@,K4Uv)u?@믓 ؐp mA']$Y8 l+@Զ=3Vj6 A+(nd.7-;bd6(ZzZoJweeQJz뫱UTxeUU'/my7n|s]4z|ZZm0N/Fk{9ن10=pC:wJ0xRFWԱ~ ԅåuxmpԔN~U @ P+ *k am 04k)]4 g=&u6=-Z Oag_Q]οֶѶov7ˉu6?rSJ]ϖRzQV*nվjeR+=oT 〺yu u ϼvm[xպuKph |ۧf2~iϙ)sÍ< |9[@> 3T'7zpeWf?p.bzX\v\7+;mu,q_֣D E㕚~.Uj⬂9nhWz;HNJHgRbr@A'(=7vPkG ZYnL, J#|0!=h3z 6ֵ"pp%yf ?CrNŻ7HΙxX^Nf"J HXN }}<|w.m(zRf?HèYPTtep#KL11ʽd W0@Vi Öh= Vj+r<@(J}iX/sr2*~8PBm;bFֻwk4|+"/ y\b0I#`&SI= X<ꐅx1y"_<$UiJ J GvhkR}ҒĴ~`R7SbIñƩsF.$z_R)U'V"cʦD.g8cT&@RکwWW g#iu>m˓wׁ#YESTSA"[q`sȀlg?lx}hdT m[:`O>XMSͶS&D%Dst=Pu"ppQQmmwo1`8A)d"]&Z3>V)pg2Jxփvچܕ܃g#@=X |l*7@vH`ȹHG"I= Yߔ:| Jro;  Э2Øc|nm0R5`y4Τ-(${>;mg_j9;05,y:ҝ&w&J2PDI>p%iCJk"KG\TٶlWV .|2̡̣3s1iBo=sшx%9RJDENW@=HBY>3ѱ\Ro@Fg#HoS1 x8!6uއ,dmD1 ]~C_wo4փ`AI I#r܅(lȽ}@+EJ,((8$q_;9#ߌȓ&\h=NLǟsO!?!?24m*P2>Pp8ܑ\6l;;A D8쑭0 5dL̂DXK5~>hzSOVTԧuZx>_BQ0Ҿ$x|;,9+) ڱa*o#azڥl; 6Ni&7Kp>Th4w \E ߽ *OsL "вe=X,֓6s-0'a;? "!&%DxW8~ ^,ZH?_8{-$`)Y+wg!'r7xy8 WFf_"IP )ҡFq-H?({ Adq}Ibm{|ϔuM@iYEs[}ΨeM77X 5]:1ZAf%X:[k_|voD{LEO@F &}|uH/4݇8b\ \zTtYC0X=",$}/8ǦR~6dFᚠS)#JtΓ\O;"O\S% :ÁL: 9݀LN4dՅ x?ќ}H)7u(qabϓ}o_bYO鲲=bYV$=B8^ՕWċ`t2݊3WJ]e[JsBʱ*ϱI3Z(ÐJj L䦟 ꑑ2䁘HjxI~b"G"(omd~2TȬ>CYa.bbZ[8]E|6~{>w;?O LL oq B<؉nkyMUY8@J(2@6@Kw#sΕ,u*o ㈥}qUS$q1%%m-륣t<Ukݷ&u(.vVo3ׯJmTvJu:kh;SyTb[J.G~̎i_>&Y 3;"dYWN˦O^ԡi>D\#Qy2$0qב@2"#!V1a {9N^B7~)LXvdiNT^ȠDݜç@(2)  {Lphwil3s}c b ̃\sx P cFi80+Uj^w%y./?D]0zG% K&T]Fö/ZwQ8\LLL[f̀S>(%'~ѵiS*a-7mWD0@#c!b3\2&0D@n 4CQC4]AϤ7!S诤:!~bNvx3A d42ƁW|$ʷ1| \H>$>{?A:_m_撺OwȦtTa_DW76^,v6N;5ۮ}--% \nosB!1᠊REpൈCqD")w KO^`QhB4}Y0&[SP^lMf 1O,L3bvD&+4s lFtυhL?O#Gwg `HI>`6H!!& l">7 Dӫ\{!QD ͞(dl9,yɩ>9Mէ DƇjyw0B@i^}PD@sJ 8a[6/mg<0At$->w,kUo\m'DεҊ6RMV;ʥE6=Ԣ\P֧qJRZjlSWXsZ":SI'r5rC 1kߍ#To bD("d\Y.]bؘbFDY$gy&}?-BggQ{Ą=ڑ>&h>Dx!3)orqn_\GgQo#Klg়ʙ\H< $kI@#@5^^~̢ 0gG"&sM_VB~>m3igNno{pŬN &)ABDhR'<.] U%{*LgqoCڦ.Ժ8cҕOz0T:lXRlDxfׇ,Xl<Y-AkrVzqBDԦZЗL qt|i뵤7=t73s=y%g?ʧ 8Ζf<l#(*R8IQ'#OMJASi+g!!@hd_:.DLXɯ/X?*yF;H&{ȀȚsfvkNԕ_M7Rv̙k6R!Z?b nE)wpasW54<\kl\bCq^존h9kA65G7[+65̯ 54,r–a7|w:3!{ OeZIg_A4#f}q!i#NEމ o!󀪋Q90L$<ɋb {eWZflF*LCBWSQdUw3Z,Pp)y/"x*SDY6ijD>bOfNk\e=kjѱUUW6joWV~;^Scu?MP0 >,Yy`τ,롐esɖ Y!:ɟ5`1f`C)NnO)]GߙDLtDIe >(we D#D"{ߥw+%Hq2\_e)O -B~r_zu~"8ҭﺀ潝*rO٭E$OD^ȬY(s+AFf1x H 7YVqS0+uMn. rzw oҞ)S(kjr' X$.JUNpMyW#vSb\vO4!ӊ̤*#Qmx#Hm*<[M+bzMnzv&7:yQ,>U,D1kw}lAL_Lh_CRPJ t:{9IDATM.pv~nm?Z,䙭N dU՜} J]80z.7/s2fdJM1ڶx ڶ?*@ة ׳=$c>9&",Ĵ QQptFdO3d (;[UUOn(TOtA-Z7TӶZ(9vs[ZMdRuַ#SZ+2\!|G_uڒl' +1[qc}*/}ilwi˺.֖0- 4*䲾c(Ҽyx* :?O3 GɣJC~!1~Hk^0r\z_J # ?ܪ%/ani]X*MSIENDB`doit-0.36.0/doc/_static/doit-text-160x60.png000077500000000000000000000136161423054503100202320ustar00rootroot00000000000000PNG  IHDR<ksBIT|d pHYstEXtSoftwarewww.inkscape.org< IDATxyǿ==.{ťͥ !*A jT^ DEQ0&jQƈ⁠(* rY2;9vv%d>3u:z ܱ@m2pL01%# ǔ3HDJO%+ `*3-`tɆ^"dBO%:9nzYUdUm:|OY^N9kO M&qJFHUײg;h\EiݽCڧ}EUR)3qK-I $tipS9J*!O}Skk_H6mRK=Lڄǀ m*-]1n$@/e앻7<=<2?%0.WEjN|SUyo YUwz>1KU (X)|sV;/:.y)Jg8@}Mة !gj3oSUyw Dz̚m?N.(縂o n5q) Jn4 `Ę/"~?$1jsa)_Q[i2PXv~0|2":DJ*\ \t>%1Qep/CbLN4mႂNY޸G[Zۄuw:;nN^w:3NX&D\!a\oVUS&UUn 9&]tpeM_RWt*F(I!f-ĸTr,HRxX=ߗsY`Hi9Q \bޜ.xiAsskw0~y<;ҬlFNw8^NhT盛/K4\ꥣ1Q:On$9.ן=(1ֳYn/w(ۘ -1fRVzV ho;x27y6iEM{e|{K4L$'gU]VUH)-!P;9ZbY .e؛ ?&v[/,1vCWbhCLs>F>)'ߝ5lS :I²‰[[,cm,|N7v1[ե7\`=n̐xMrŊkz\:_a7VbydAS`3􎈮Gbx:^)]`~RblZP8gbDk6| c s 8N&}#SVꜜyn:v:vdE=￴xS\'?-/?Ljm|\57yUC׈кf@bD7]l"c)cZMdhxpĘ7|cN9 #RDuq6oc/K ~dVhj \B ’N<n*1ItVH$\zk7GH6S8);k}?@:`.DžtՊzUmppufG+FAPi?RQ͑77>z)NqK(w\RG"y " #1-Fr&!n[iLJ 16D+J&.xQq!`1{9.lXQvmmzxp\kN<_B,Sy^jNQMӻ**(,WWGdRoŒBb\5 T3#R:LG%9xn+`vK y)==`_Ul ngiaęݺU*>ӯ?ݍG:%'gYYO /;!ŋ _PW9v@A}Kt8KF yYv)uJդHi4tDO"~ %YiT g|񃢸llgvPoeónwLꦦk/b8x-j}^(8$d^ȄYR#n0mSGN O4oO<f&%^)&(JȪZ\1(|ںvf|{֋T\nUUy_ Ҹ)H T:G:RAt9GfOB ]!ZqWb^~K4>1wgׇll# q/)wWEv:'zkUUu~MWt!5|}E~7 0!DJbKJ)5 c#%Ʈ{\bcAqH0N-] 7S2Uฬ<Unťw8a̔t.3D,0X*,*iن逢+!8WF'B&Cۯ.IXw g- %^pWƊF/tQ "|&|4XaU3±{YՎzX\T4'V4:W\Ttq׻AQJ'r4 `;^1$dARw"h'|I=a$>4JE yFqHfܫefOXN1\"f͜HH.̯;C0 BrHy &'eMJ" Is5I8b``y!1VzvO4] mv{C`09[n\T5L9Ms iXm3|Su]&@]PFBs/A ^c-]ў ?SoPƀ eg"|uE2B჊qڴR({p!!aS7 Y,މRfzK<:mf*FOpG[F7_nnsHQ`%dE~BB 9<Fwsm0X@6 4=,ٹCqUUj5gf}LΌо%C[.Ct'I oGQB>|Qq[M׀ܕ6EJZ))mm+a*g.Yd\ssrJ~z ~]js,BBv#RQKzf֕%nLR܈u:u)GK]: apB3\ܼi p9.*8xT';'dgm<3kvCzxP\i55^3O0 Y9Iu#TZיn4a$j:BayQ.jq8d ' #Z=bUd[m%%'ROaAi%n7VEiN$1S1>vQ̫좖֩`"0LWHѳ-`X0O$AKK˽z@A¨c9>*R= _N4@3-n᛿:%tDfl^?-ח\όs<!IJbd#-VwXv6QfmFĘY#6Op mx24ģg3L%4GC $%@BXRhƽ`qukecjaaV늟le+x֤/:$^W"Rx11 IR#]cc]10䠵ĘJ3 ZPVVk׫~dI}ee0X6&zrIra Kc"ʡ EH L%ҿ.5܊ҼU^'+l0l2H*0W~)rlPմ>Մ#_ NVe&%q:o'$_(ʻ``ɣE<_@y~p!'XFq\^d{dZZ~yMnoCiB3IK3aIl_W/56v;qDJGC; c3f86-@,(ȟ.l3J8.j}gncn^#C%4mVϟwrV^O0,xC'`eKh殼gxEU,x |e_[[< 5fȐᘒ`cJF)1IENDB`doit-0.36.0/doc/_static/doit.png000066400000000000000000000314051423054503100163770ustar00rootroot00000000000000PNG  IHDR<HsBIT|d pHYsDDptEXtSoftwarewww.inkscape.org< IDATxwUևLO`%*0 "V1*IW״ &T ]X],*9CIg=ǽ5]]]fH><=]}VuU׽H \ ccccc׆>#EQ\`[+6 X±O& ;A<,T󒊊JKw&"1Nf^-Tl"l}EE>Iaǐ5tVO$+lN^}ccmm{m6_tB&Yud*+P^>wb0Hd[76.+V6E"ڰgdcccc S>}PZ0{/~;w2N$*wuNNUGپBN_װ(ʛoU y>66666I_r}s"7^ nODGz>E` V١H`TlwuJiFOli9saGGG!{g=Q[G>^串}pN!HG@Qv3*K4 `/|ǠnR+"Omf ـox70ydڵ;mգ*)yd[k'm[NיP0PW Z| uh(TX&^x+E3_/|T!r Ȑ -A賱٘*U]=ɪxZZ'~IEV疕m`8#(ySn /=|ENH/LEKӾXt1͏0;.オjOMv xj7xOrgD\5WVns^YK\m_oni֖P0k!GPU0@xrx@Q8N'8rA>F=;̴K.D/<BōUU?/#Zy߱ڵg檪zOt8 yŪX%s33^]Eo~ `6]A A~䠪Z65FuPU L0 ŌVW+9XJ\ &fM0WmCDKt0*{yiژ7ni]#jtTկ;F !{˿f1U~nJe6tUZoL^jgǵ)":\ }>3D4—k 3?F} CiCt8R3F$PQ]TN6'u}55Ӧ54<ݩ/=_W7{bIɟCD|3h[["Uhp!'`]LD!(NpM0;(l ?etPph]PZ63PqKž63iT:# qoæ+G׊@鬙\Z}55ǚocm]/+B_"YGc:W($F !'x_2es(T5CV9 hhy˦ghD4ל59B66Y(PE}q\i}R76n %%CKJv8}>GsԤxPʈs{ߕe$(EfE*tBDE} Оln4祩P?` (64'U.פf _m)Z?{"h8-m0gwmGf{^R^~e3gG㯅ۘ[*N;ݻq𸱟 ~yy'@?I~T'LS MV+˅(ʅ xXPW ZP TU[gZhlPqa G!zy"ollSY gWH}Dt;PXp7fY }y(gO:nr,B] />P"7M`tc yD38(Twu|QWSaH٢ pf.JQ/_mzϣ \3 ̍tlBD%Q3 {VŃD, I1e||n?Or{u(g44< 跗8mS/׵۹\g{}9_TC3gAeϛ "*EUE߈^b|*B&:Z3|3ZJhO"FDwѫDgX` -$^&fNj%;Z[?O`k7㶏.[>kXP(']QVU)>D"]+409#q2!Mz ' }+ҤAyvqq@QN2FMEOOzՖIyn@l`N|dID;ёDt5#o r^Ã!t;C7k5:}M߉#<.n緷7۟>U;\;@?|7;;ی} $ j1(LfB;[19̹=(O)Q~*>$"2sl WQ3H`2s!Ϫ\4g+HTybr 2s6y NuXK(A8ysJاE^JB0e=ƪg%%q8G;FzNXڝ|AAw~K3.ץPpT]VvD|Y;A*F[YΚ d1o fлEy U8 "ebK4\@0 s:5 H 4qI!!":IAmoۜ \᷶k2$~Ԡ ` r߿J^طF]T^rG6{uq~`ZCûoռnIZ\3_Ɨޮ;0XYt+T9iRaK 5"Y2שrdAj8(+QAUm(J+DI87DK/C ҖopBNf!j Q|Xlj!H2 gvzZ` -q_'Zmf|nDhNB`qѪ ab=t+cVz8Z@19NԶyC:D&vue0`>%%muNg=g?AH-ŐR6<ˬߌP10xrz T,jñQu ::0NC MZ#*Ppp5F G2wDo$4򸛥P!/oocod(D4mSaJ#sŒ "M= ]-*dԪO"mT+z%I `n.aDt&frC< =3+''1sDia!VWB`h0M f.X.2q{fp"T9}}~FS&uxPh?Gv8LWhDr2s4oՏb2#ϚfS`ti}M?5KrNkgo8,BqBR"=į|1 *vraq9DI\IyZ!PӵQUP*9{*jSTZV U;#)>%BB~xJ~NYANiۀ3^Xi1Y•1h0S6 Y9]2腊Rݳ';k4m C"E탚'\}&[Uv6 m0<ѥ9fB|S`U#DhJL ݂ENC\!&vy#]L0"| qmO}k0wFB]\mkL&t_ζ @hC\L!~i)Շlc\i|7׼tHk.@! `>MΡI oC.CnB/O{<{< %r,#ruex%ӗpl[0JYBT'CRX_~B(R id,AU-F>QjEHoG5# GSmkL0rI>"/Y4Ry" z§@CBܕ>8, 7%쿍9ijT~}(kdy\""BAj$N 3Z ~ ; ~s!47AN!&n!|ƌiW_60rMKSBDea@<'A$6qdō&"" @/GA%X I,I&&)>#n3GO۶wv}Yt0V|k<ޥVpM,)[ t>!_FgbvC:d4H$s@<||Nuƺ'۹*B3kBo;ڠK1՘u6D@ 10|/3+?b-N} r7Q+5џZoX4BD{ADh ; 3?H=3 a2 6AL̳ fscBo)H*H_@1M3!w|#`[\@;3̳Yf[y%3 Boeҵ('"PhIDYpvD"EhtLowFg4'uOwu:5Ppt3E,kJ$+j*U#SO2T՜e*U!BEWie-z5e9&~ 5[H\S=nleQ7CB88xH )! ip4=#&DGu|v繇eU$kmY/v 1̜M8[&PAD![]LN{e9Ƃ^$b"{J~_w{A/7솯"Di-$B)MʼnVƲ.x&-U)ݣ^]VmvB ϘyqJ&7@E-k)~D,%(̶2?+'z-NIR/20t@:p-r mV2~`Ùy3g #g&72Urְ- / ;q~&37-DN:.(_ { 66$7̡ hf 7 P=G1sڼT6{ >hfBlrm1ƽooaE,֥F/#]o3y{ȓF}'Ȼޤߦ\߷%?W}:aE96'-$ij){ (JY6ZDsH'Dta\#oIHffL DwGM\|K͓> ; BD9+惦}E]\|e:|O0F~mv7螼Ks$ʔۚ xHD{"D{Gb-GR炈"D-iߒU1B=={}GvqTUեnY Z{ڎ0yHBNih8P(ő!$8R㩩ԅ&7//چ8~E)OXJaD~E@Q\H.(Tƃ'z# ᠹbyAk6Zք +ZY[=[f+^'Vd%4+axVnM%rPMEZCW+$}$=̜vlHuԜ"_b ٕ Zwhk㓖 iv-3 3jf"p8Ҵɷo_Mrk'K#.DwT'Мϟ (!n,o̲BRhd]W[tMRHUEcP-j%è*̚D&Gr#N,l}@yiH9;ͶVgpM1sISAZiM1HN\д벬a~R`6" BDeh޻EAE)MʼnHN8$I(@K?!-/ : ~=h{i|CB7юMKS- k{7s|]&I]}k4z4OP1X+teWTV49=$)HJtyEś WRmt]>|4`nw˗_LIf!׼'FinSƒ*ƔV۹\{lrs3Ust0ai,h`4q7/1q&D@Q@AUjAU]P&$=kGj#yAxUkP`3!YqC?BLAU-JaȔvSa:vf=ٍ3iBE6D:3]2i |MxTfԬ2KkK$TXisE3#TlMDl.F &XБ=ф'N;|} Kv׏4? c=9.m.}=з."%B9m/ĻA{6Ҥ!uN1^%xxN"׿I[t:25%V 0j*,R e􉋲Px"35ìa5gB*(Qzb"ݫDy(3MYф '2GhmӉFAL@jq>1@ʭphO lWJ1AM#svfI4tU-kZ/@!Dd%sKm<7;vOr{;CC;w?w9{Q06-M>#9Z1+DV} a&TںX+FK8Kk=~P脦D"W[,`y4mT]UV6z_ ru&KE)Hܣm 0M>w[+(#!TU|r?#ɭ~ I_@m+ T`Iv"*b,f;d@V&:M#a?AZ`ɿ38 ++ڀ;X6J:*Tr5H3MvOUX0B{nlBE!*ˇ2 Y(p}&,mp~}ƪx˷bG{K+*1iJ}υBǩwfmY$ --GooOFD]\gB+wdbKEa j>*@L0zuHs9mCjhptd5v$S)i>l~DtDXliBb-|8PADgAh:-'A&MIxIa6+ "0T+"2[[MW&d*4AU Zf1k}G3^3ꂈ"{@jȼ3PMF עb );~ma93`1UjJ}!|dW}f-"ˎv{ڣzZ_= ]s~1MMG?fcNuI;)_D"3#zHeE!TOYX3byP$+i\g1WF+rB@QDf2Uhtm{E7˷"WutՐ&Tt"%ߞ3 3vӚ70wH|v,L h1٪ fUV5"S Ά?C8j>g֡ i!2{H7 `z&B`t}Ԍ V 3tn5T9UHڋ 'ȤL< •!vr ~`t5 `Dh Z )TtT)|QpU]myuuN\n}mm'袯%_FoL(t_nuB!$+D=GXiVV.U@Q(֢W9v86oz2un;EP!\V[ PEy b xBVa!!Hߦѓ QSA[ 0w`ӄ\?1Xgi?곢=;V'!"ׇDm\+%Y?8,LZlI4"I51f-,f@8!+g5H3=SZv)q5)@|#-N8@RSd|^dI޶8Wo"<=r":]j !LMG$s ":׸0Aܧ19NU6;:t~NgͤgsUMM ?D! hzW;EMu4,(T,j釐2X* .O=1(A8A~'S#(~Gj8 '\iH:e 3Z֖T^|3ˈ 9ߌ#]UDaD/3/!!y}-UXU4GOK*yf1!ϻD19YҴg\LPM3 Q f~^0 ~JD!9 &x"L+sʹcfN)Ľ1L 1{CLtx)I95QaPɽ3͗WfYܻ^ hDm *z ~W0fG:S 1 )\a(AhO`r85DjY+BV\NsKuuN!pWu|)uti, s! ${R{Y|<X@k@QVK_*P0NT a2vAU= U+$b2qd= [ZZBDCDLױ9K[V$_\`\ !X !P,=GH~35e&$ Xc%L`Gҷ V_ `{f0·s*BmY![dCz+Ad{<Bx_Q&[ԇ ycC9]]C<bll`U} ?Ao>\ lګ'jkx\iUkBАӗƟ++_PV`+EJg]~bGF| I Vs:b8xbFz BK3mz s2f#lvǑWBD|s̹4G 7&jG<9MvDt g9(n(#eoTjOjVfΘ6~& rq qoz -ly0j%=)(  @3$p΄1"7·?[gm )\*ۊIٺYRr~[s"ѱ0z+DY7KKPXlMM=^ \kvF܌"h,#9&B`TI<@8} \nlYHugoQ>etJm6DTC·M)~PySŒPADj]ݼ}Z EvvN)[r SvtiCG?6ܶ.hlLW'/2g0Ԗ !q>T\EG{xiMo1bcc3X*`^Yy%%gSKw5<6ƻZ[-;yͪ{aQ~+, ntK@Q&@33dcccccbYxG̨t8Ukc~6nliHrDDTT 7>40wv>~u9CX4㡍FB̮xtI}[(3V-1%&;`%xa(t1zL Rt2$ /Tl mllllllLK} WRrTx~)le2D]Y(?l-ThY]anD}i.&}ii8|Ӵ :! BBƽ55wxu8mrmme)LN8|Y Dlllllll6E*:$r]]}ϡNIT* pnM$H,(xzS{{3 ,hIENDB`doit-0.36.0/doc/_static/external.png000066400000000000000000000002451423054503100172600ustar00rootroot00000000000000PNG  IHDR ?PLTEf3̙ffDtRNSKF8IDATW%A@A"OT$xl:rBΞ!/Y5fIENDB`doit-0.36.0/doc/_static/favico.ico000066400000000000000000000037351423054503100167020ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsktEXtSoftwarewww.inkscape.org<ZIDATXW{pTڻGww}^c`Lm #R 1Ө-L2A-cb%թPb$#HBB6ٽ_p/]$ι9߹0 _WY)Q`,2&QwmM_[۶9"c,N~fY'&aǶ]e`\|&`M߿9)&D /8gMΎ eeۢNǹԚo:78ɊsiQLE5[qgGǺ) /`D"JqNwԆBe;хGC9鴳#Pb4O7x6FB6GU}t}qNV:nDnШT*CS{2yG`H&|z=qN6kp@Wy5YR|p<^abwb/ 13|>逮o"|2ٹ(4F,0/h*P(?myO=TA_+Āx0F;o hDD^YiDaLb^\>_jFZ(=!Jmj肐m) =|I/#n[}k**^wVny~kõsHjI yCKvi8Q]B9mҴ{gڜTSHA✎%Njmq8 U Qtq3MSüxmtakJc11-">nk4zX2sfӴ159"g,Oۤix!& [=DKS%ab@tV/0&TK٪`V~ T>eV?LEfq _8~Ɣq\&/+!n3V+}?KV %8?:8(&T~ #ʕDeLsg#ɳӮ[p&Q1V/y  T90ʩ|xl1MUxQ (Uu0+#eyUU3_dweL":dY] lYW2+u *Wޮ6>2&?~ܹ1+zzF|4m"j'cX4{]7sܱAQeܬ(XYY7IQx}c&s6iڢey:d [iI&A4Sgo;޻[ `x?qy8|>]2N[9RPh:qN;KUG +ݩ%sڨi ڬڨ[;S 76G"t<8n9f +285rG{{k^͕~Cٓ=|&c _ŅiP}zeyޙJ]$iO, O*/ wb x5m^)Hd3b=]aSE_굡PL2y8Ti8<VN95ۊ_=9aO,[XAq#!DVS(y~PBĻݛx"ZhakhtWBAqv_,ǘ]2Nn IFE :lK ;bP,R(@/[ٚ.p0,+I5c$)>u/}hY/>`Zg7ȲPhdEXΝuF#77h-IENDB`doit-0.36.0/doc/_static/pygarden.png000066400000000000000000000350721423054503100172550ustar00rootroot00000000000000PNG  IHDRxx9d6sBIT|d pHYstEXtSoftwarewww.inkscape.org< IDATx}wxT;nMlBH BHKh "DTP>(E H5 [6Cz^$$! xgdg{g̙s O䂩 <ţSp<% Sp<% Sp<% Sp<% +P :0 Ӛ Uv{3^Hgmnq,/4YG6PWH}*run.!;JR'9M8l3=!_[Z[fNg"jEqa̜B'nJ_/L QƼP$&:Ӊ}GNl9' n0YkBPGSؼb3/]5qfěwu۝|~݆b#X0MP/=&Aqn;ueX9& NQiu-TnA.ucJo\:n&m5ĄDyGg'&As՘75@m<yX[ZېjX}Ԑ2kRGٮ-°X!{gd1G! \kה#ПDᶋM:՝xBO7ZP+MCcCcV}* >2C'6pCSW8Ifž˙qƋmC!!a^ք/6ǣu &,cEX`NzJҵAsܴwo%!g»! S79 ۴ _ޝfpDVB(> "&!vV ۑe04&\xwѴO|SW`/Jl ٦J{5o1/Sp_%hV`&y4E!?5Z+5_x?0-)5?MM^Ock*z-7 `~x5=Wl,Y{5i/ǢvaUDvh_'jk& Jؤ3YZUʊ%I~+兿7Mnw7MZ{lҦɜov׺ϳh'i­Jqy9ن9/++V0 I$qZb2יNp[9]؄Q ;4rpS8<0떏"  KFݽb֒/ìy'w~+uUҊ'e)ox{ƕuE΢Z]!忂z}ՁH̦ϷvPo,; \ĄJYP(AH,+$cwb7n*])]%r9 Z-HTbzWOQxQ/' w8u FNǾ/>vx>A"fZ:7+-;2Wg#i7q4v eez7zFH& '"MJd=[{AղҲ' 6tA)D 8ui茳Gc!p+#8[Fπn)T h ͉[Vb5{kՀ/,(B I1][Q7Ns]eO;rNzNߋSČo`zGsq*9AYdWvmiU)Xd 숴7Ĝb7_#F v="(IVFƴ/E9l/݉G}Վ B_}wFu[ʌ|I_HEuVadeH:uQu'1Os,b6+zЀMtg3A7_} taw*Gzr">D,&לbw]J+~8-`j?We9O1u_Ja/H.(]]V.V^O9, FlYxTgׁYV!8~o(=^c0Qyݦ-U{]Fj%5  'l[4jea('J!JЧ< T ̎Xjr32LI;i,X02!\_m~ PSDnE1"z:ZxO̲d!6W]c]}.8w04'ѿ(ҙ:S|lBa&}Դ3X]QDs-Ҩ+bw),{<  =n Jj|ĨwcUrq@xr+Y U!ag`Ÿp( ;#Y'2R10`Y6b2~iBzI{7X}PNQX\<}n )&#ğƮ5+dWpqs ʚ75H#74 %CD6t&xf s!ÄM"գRNۋ/G C0 =*[ɶbNo>t cp#:<< \8)o ǁ<"@ӶQ0qʞ .aЇluEjёBCAQ]*ٛXbzj'ݛɉX:s*K/Iwz5Gքiդ0D}-p0-]g藓av=v/>Gx6wɚMF|\m"M3beUڔGz%wH&?:q5_;uNʻ7 !q+# ?L|Q iR,?j4s=_fyj/vi"/=żEh&5lwFeä.)%lR橇Y^ ~.a J'WprvbƧK)iހ,;XvTsYd>1[dM|>ǧt|bFYɈ_9S/sZSwǨ2,uJ0Iت(} =g+2ƷXxHtl4ad#_}Gv-\_#O kcTݺ-mNnw#>曝|-Z^~D8?e228 mJO|9mt\%PH,yZP/˝?{nK5! Xtpdd]6m_MU^Gp'ox\ ~Jz7?l%S8;K٩i'Z|ث)V-v$y 2bDvɾBE z0v;H| kA 7*q2;jU T@ݢui$R)Vϝ~M}cI1K.\SYD(؀0 " Ǖr9X5 ze>j5@ko&C. ZH\=۩No`+:u$T&CCT$`rS,޾^ ںٖnfԩ,V.5 [{F Z6i|.ӽVh,Zwz&wpޔZ~#laoթ +wrV;<(23X)ɐ[}"ef5;+8 E0\Ub`9hO܀\:J%/((gM,=Vujf L(EC,_.rUD/:ͣ B_VھN=<3YjHeaJw,9 &>prugj6cko"3`6>^$~ [@9rѺ"$!E;. 1<] 7&SעT w]$8| f+1e'qcOML/k70yɷS!!++Ean.(!GoX-h+VmӏuV5tqw4oN䣸e)X&5^zU"ʒ^^gŹπL Gp6kͰ,E[q\ PZ#3'엾u&+b/epGsyP>I| "s +'h-t+|{~;-b1ܕo;= J)\\oYǔ&_B&loqvq5PJ= yyBaje0}6!ڸ4Z)<%TTBWZӇpͥz-z oźmx)xwt-CRnm44`{qWwO^}C_il/xS( ĕ"h|l}6\hٮFpmxz!aX9Dydˌ1]t^w%2'xE=in&I^*p9qa ~S;,,վoTdEUEU}Ay>JZjyʌl '#WXDmMi>xrxŅ6]Rn>w͗lc7#鍵H7&l-|1vh-`4RFc?}8IN P$'P\.M7Tsr#4LD);H^,Z=ժhj fY+ZEVB aj-Cpx מ9SRyДZg 6Ǿa.+,.&ŕ m&\f@5W: gQgxc]6|0{.r{w_L-foǽmʺ ?.Qȯr>}p\IÈƻݰiB{`gSPcϷP/}9Jr-B^-W1uZ/r_纤+6n9jL(,}@8Yhܺ Zw 5 [ {>snCQz/}5,wL18tlW7v`j>~6Rl:F"dIHѡF"׿vY{pL=`٠++X,P\yoa%Q{pԀB>d_ؿ~osW-9y}\t5*-ŋy%Ǔ誜-xo> *bV*VnE)lX56<=h~i ~8B55"]2X6kzd"jj@F(Ͽ:cb`yf+vnydՒ5g?n>hM|]aVZoHJO,<Ӣf: I+ Xn=aT8E^[Nr4 ~EE:&=rtEXM[PXU1aBRAC?{ʙ K 1ox<;ubHz@ [ efDUMժ5F,^ l_|  r8 ގl╧*Vdtf|"_86xW*nKZ54ZjE ee]\:sȅbB1wxN&U(~t\rxLeŘdFj u!V~m3Sl,NYJ-kժq} k\"S[ ߋáP*zo@L*H.ݖ )/Tv/odnAʝxx o[ )4 ')}nk@p~TNi-A~N}lab6ݷh l:Cx23AXv$LknդN,Mr~:qJ~2OZ);ztZ'V^'墳)d! DyH+Ʀ /nz ; 7z'fPvbVb.? er+Z#J V*/&+(xqLs18ְ+p0$ASaS!B'h-: %(X=>^_ϼԛ2B/<쮄:/Z F]2#_'j;cq5i!Z 1N|v2zI:Gf,.+VLA:?ߺFΥPB6!O ' da m#f߸X|(AĦn6܇U8 [(; ICw\c@k}2nÜQP8iIFh0^p5_Mŀ6辳$EfWʌα{*"RM ϷY tP mͯ.&p3B |&_Ji;4(y zmF燠 0 ~l2Ɗc5%ef~^7̸8%Y_k%- tX4FCB:b/e DRX6)fD]剎hJw2BW"^զ5&X Pvb 67yW7XA?PY}' r@ofaϥ,ɼUhJHDlsrW"YE͍M6hep" m}cIpDbWJ  =ZkYhnUdžlrs[ NZ%!#`*#$ -o4K!u[d[MyZbNNk<|?}գn',Þ?Z._99XMǃ,V\*y.axc[hʀ0V+l+2o$ӣ{v'i$ aVͦ:{Sдׄ0x7OҤ+ !0 r2y#)W/&_f˷Ϳq6ۚXm]]'"*?NJ׎Q aò6td4wF`t#RY<dzX*&ėp>V^?ES?ߍSƒӮ4*G/ީ@J{T5 nPGRW]:Ji:5` -L#+FB !5p"B ͹t !5(a!^.Bjvs_RQľru}lFǩ*q5_ZN u49̳D'^` SёRw8DQ>\a?+ a搽qe C*ݩH #l52M*̨Z!{, %~(K*15p `+OaU *YG!` }U ؽBڞ `.wtؗqg6Txj+Ϯ`GjP֨*򇮚q^G )l#r{a[>lR%vX%*-čŝe#L94GX.j4BZ(#vPJ!;š{aG]L}.R !$!؇PJV+pdH ݐ9Tܩ,ww:GBRzAuG@kbR:RRzQV}*?^WY--0ؕj?:QBHw ף*ȖXNjTcɪ :؉+͟7TSA.>ط@843^ _AZ~w6a_8+7LBi2鰯=`Ѱ[n]:X~:so}˲j?(i`ߌ8 r~y"ȓNX`#Cu;d's v0xL{GǙQ^7S@f2%,a })ܮzj고B|qi{+H^e/ϩ)%E^`)!< JWRJB>qԁBv;E)w%Y%|J9'@/oCp׳**N,BHOO˴^G)G@%խ@; у$J)(ktX]or%(_C'رtp|Mxn%QuhG)-}G L)5îwlxvWJimyƇR܋/VPJ'{<9x,V;υ]s=*r5 0v#t{I=_< Mq*e̤ӭ!D r$gF؇}KOWWp<% Sp<% YbIENDB`doit-0.36.0/doc/_static/python-powered-w-100x40.png000066400000000000000000000101131423054503100215130ustar00rootroot00000000000000PNG  IHDRd(x_CsBIT|dtEXtSoftwarewww.inkscape.org<IDATh{tSu$MmZG<*q`]pAf]>( PA۴iGMrss_GIIۤ 9>~?D# G@hooݬQIgO#!$VZ!JW[_oF~ww7qƋ72hN@ED*峧9}+%%{1 Houd/شKGmzgϞ_~f#bHV p[AFBeN_>?OlV|>f#b%Zl ~v)g[md 8ڂ v=ўXyA>Iww7)˜%C4.8 &>?y):YyԺ:uggy$+Wvb&[ܳt=\yqq'lڴl6V^}̙3{,Yb=q℺&nT*Ux돤Hz.9A9sl ,q:ݻ gΜђ$~"==~G,&M kQ+ o>㏷^0… -[d2~ٲeVSqZfMM #-*)ݳgoɾ]vwܙM~?aÆف͛7IRV}… E(Buukĉ< 1RNc .NyA-wL&hPdB\tIIPXXg2 K.QE̤"+u$c ${j6u7Haܚ98兺%мz`ySYPV )UF1vońF9O^j*Hz$ J|+WDҗ/_;m޼yJ Zq\ sܹs7n,?mD>GRq>lӖ-[ ׮][h*_xm:yz@__nD~1~n% Ojz10l۶ HSShX^s϶d8FC(š5kg̘ѷhѢ|Nè``<q1U#8EQh4CA.iB1XEN'ʑ$94 4vQgt  aOh:?fF)h0 y Pye'XBhڡAV1rssC!ƚ(EcY`1bw' qLKW\2a^뽷ALD|i hF>%Aee|UIE0>̋.#D+EBp*j@L 3`0͊gϪUVǰnF EU2掿mig6k鿧A. 0Zg?$8j***xiuo/ڻ>2>R_dϺ}O;Y^W?{Jw8(rӔ ű}"=E= 0Y<*Em:țϯsi҂֏j Ş] ?|аu/*+:cV/[!)%.I}gV-]GΥu$#u%S )73*o?2UCEfכ;6,Yf9r^z{isGKb 9O5cBM+C3 :!xz(IOM 7wȚ,PCsCہ^bC-v䎉9E5'Uηbδƾ>ݤ.JM2eηZ*Өe 0x[C'2ץ*b],!uynE^l +ZKW|ܣqxasE\Jf]:8)kR yS nR 2 W/S{AשlZX]վ {j;@i~Z9T\w _%'w!ƾ 0m00>qU2Qpi!3f̮*b&VL8٠jN6gV2d}Dsͩ9 RIO7j+s-=QI)+1N2JշڈWyfر` ɭ0/d%{"ZLk1Gvc/]hwxȂK2gT$ϩ0i^Q p\*+<,4ehOӧytA7 :bW))'l{탯>UIqy{BˊsW~)osx'.wJl~P,P֧^?ÖPɥL:z9~" !VrIbh\DFxl3|$..%.@Ao$C OZAKk8f0cuP^0ʖ08AD" RU:0U)]F~I^ u"!IENDB`doit-0.36.0/doc/_static/requests.models-blue.png000066400000000000000000001240421423054503100215220ustar00rootroot00000000000000PNG  IHDR{}bKGD IDATxy|[.hx,K $'$ʥ02-K[ڒeIhM;. mI di!Li03 g!qbǻe[ ɲ$vl?-(s{QEBRRzDDDDDDDDDDDDDDt1,&"""""""""""""Z-@@DDDDDD4~"(/~r|ҍ>?OǪ."b""""""R_~?>c~+}X>U jhpYS ЪhY3~V%ȷѩTЩ0z&"""""°f0nnW/3I5늈0JDblaBAA?پjOcF^%@;$GU˗c׉&ð/>?F~x}£>?F=c=~P)0WX`P{nW([ "DQ@`} : Qj4b4*T*hƶh Q(A7]ǐG Ba^?ܾO`9E0MXp\,@ZX1O qZ5-LDDDDDDs] b"""""H0cÐLJX+O?0&_tX z5jhTשU"hncXLDDDDD4߹}"^}pAZ*X%""UKA@FEz5u$Hԩa%2\h>qua.\*al=[LR"V Hbxxװ5QdbXLDDDDD4W >Y]A)( $5H ^VxDDDDDDDDDDDDsC]N/z^"ŅyxGLc( RH 1E2ϏQ/:F=I"iNS<Έ"=Z( =&"""""1Ea7چB5J%"IQd^RzhDDDDDDb"""""H06F۰n-D?Hk!V+fb""""""E>F .Qic"Ңȋ#-ZDDDDDD7'yȅ~\~?5hj҄XzdXmLDDDDDt(N}N Qd1u @-yz'DADDDDDDWa1l"\((h*J`Ӄ1Ѵ1,&"""""M#|=ϐx 4Loo=Ng[_((3,^$g#"""""jTJh>"uc.D??> -۶ʟ=~{eVw+3n1 .; ќhy"N8:M<@z#q_,b F|{g;%Ze1׉kîaN """""DDDDDD3/1^! }_PUz$ӧNNCն|Vg|k< m؍J(1,&"""""AȤxF:k6.Bs7gh2@DDDDD4_|h`݉G;CDDzZ <=C}/AӿÁynŸ~~w}: S|#~ p~=*ۇsqאw"_= ]`ALe:xi.v }Uy;8܏QyCn|}ݧKւ$ ֚s??Q,I>j`!#~Oў9*zQ? _Ż.bxMc܂XǥQ."+VzX]b"""""< E7Q㿻 mP IOdǶގFo`7 8{A|{둨 jARp/ԁC?| ~[رgxuX!Ms l[=]xͯQ}a<+#ǾJAb *Su#W;8?_hF^[ɻ~W~_@_Q|= Emn% QaXLDDDDD4C @3"omRChAٗIJD`Zsr'vstϱ'Tű^4n/ދQeAfu<^I +s~O_Fd ' L#qXDֈhJ-NTyX}@Ѕ4/ )jR_l ^Ceû\7>|5vaO!E3JnfwOqT'hZE(T=#0 QGKw^U u=x1`wԻ65d4ִ]ho0,C_`}!aSWp4ډZ=4~gqdD"'>Z@⚻1z%7𩳑!A4OY|{!jUHgtg\^C """""H fHf.bpg0vaGr+|v~WǡGų/k^㯕0lw5aNjS y@Ő_;E8"dhQDbXLDDDDD4C,qz#1-C?r( 3[ w߾:|+G-G P6翮D 90QT"ۢQ6B~6_ ꫬ){q~@D~ (sND\-*7j~x7r!>i @HD8>A\h< l!VqŠёN4^^vz~ x|5.gfjc2NU 䝈b\qVzHDDDDDDEstᱬQ;N^E/$~_|k$AY/O߿WhD=/} *nS!|i琳&}RAw_/n׾gX|ݍN>_Z0;cw[`r߄o C{s")bu|姸KzEN/__Ο^8~?͸ȷ3EHމ~b೻{7HXrVtǧֆQρ=!}gHz0*S,{'a#o_GIjHP'gh^hx;#x=OQ>'jFpg}p?v?r5}EoJi0<<D?nK۩j$$$ 11EBBt[xbb񈋋Ctt zPb"""""xQ4*gp.э#c\H[8hFc``r,ݖ>ߏj'>*J0yѢEnKAt\\\X$%%'""""a1,KBN;E̛b4ocłĨXzFvbb"g덎tbtt>|Ar=i_dDEEؾQ`XLDDDDDt|>:;;ՅN˷4֞1wcc {m?.xބZ(I<*M@@AQṔ5$dee]דZfW-U=100͆jz``N3׏i'&&}Җ4DDDDDkݍ@GG:;;j5-33Yf ߠvd*wD SŋWҼX>~VQ7Cn }z7!!AyKt;))iq8N[ss3V+l6ZZZ`-?^i%gffFL3>FLj=n/DP 4/ QFi!F d i&]kp8¶ώ%c-++Gړ|ڊ ==Wގ9 nii p8&iu322:>QDӋ.#n y:9M5*djEJQ@!'q+L%ϗ_BsE,) ݶ޼1:,ҩ񠳳sߓuIp-/^^1K'' 9tHII4 g5ɓ'>@?bbbPVV }sy;~-:I!\!R!Q"o _UABBVZիW[rrÏxN6m 6hY)|-O4<< ]]]A;;;)t:FTTRRR4!55U/55At:hbXLDDDD4\.*}U?sgc߫1Y8!niiАx1|>.]*;v t~? ֮]raʕaΨ׏~nfOhhjj7龞 AkfL&ffyMM;C^[nAYY֮]u!==߈׏a?<> ǮZKzv@x\ӫ`)ժU];q]ߋ~O$f ZVh4N: -(NVp_GG@VU* ==]XNKKH͜FDDD b""""ZFGGVZn]>z}f#655Qd bb '''(f pcx^Ԅ˓."F~x .=a v;:mVߺ-#@4CD]Z 1cb4jhTRp>^gΜ+𸼼|y6###hnnjEkk+ZZZ"nnnyQNN}ZŎ?cٲer(f*=)D^?*o'ď=F~8}"\O\Ko54vT>{A^·}pmQjnp|-q9yɓ'Ve˖ɓ(*=yrx$755n?>yDOCeĮQ&85v`%>0u[BDDDa1k3^8P:SP ݘڅ^|cC@ T KWUU jxڵ(--S/n7|M<8~8JKKi&<裈z?">nO/ >?zYg{+ c-K#wlq8" ":*W!@tWS Ъ>>v^-@=>E7ڵk*++Q^^;w644ȯ*:u G>@x9aw6=Tk'!))I!Bwww 94dZAۀ;ӄ̑𾔈DDDDjB`!W-OML&hZ0X`r&g IDATU: 빹QpO(N N pJtttСCػw/ڰ~zl۶ sό~c!//oE)\h{$~ahQ|{G|R2J Xn8*_;^S K5J-@31 VTTgő#G-[`Æ s:Tٳg{gh4,^Xʰdɒ9'Su];Y5Yw 愁-A Zh4A-322,ˁrFFFw"!""ưfȈ|W:)p8`. \X#==FQ^ NZ.H JC.]0@"55upi3h4 Mr(\QQFjU9·ٳطo^{5t:|+_7MX,6+n㥗^Rz(3{͛7c˖-&hlA5N> ˅LXB~]~wdr:lvhii3t:dggJ6 aw s|>wttpȷu#N gee!-- YYYAADDD!  VZp8mmmA-T*ӧυAApSS܌VĝfVt)mYYYs0MBedd`ʕr;-Rz3ȑ#سgyo|cޟlap`߾}x144zO?4JJJڌ~(>z(nluM ttttɟ7U+f3C"""AQM6 }}}Atb)eӉ 622`, @b 776<<3g,8ty֭[ZbӉ_< jkkgx$]|bb"V\9//ThlldkZ_ʽAO^'SYYYCDD41,&"""Ϧ[֠J`N)O eee铨ZEV Kk#Of0O"?UÕp0 rŰ5^/^y~۷owզӱPbI`oRl߾<ȼ^/Ν;'P]]T},(--eW9rmҵõ29''? 4ʭ;;;ap8s:j#--Md0F# z{HDDDa0,&"""BU`hhH^) 233O 6 lSja2†HLLTxOh6 ܹsr8|ItuuAbٲerUaYY.]pg(xw{n9rذa6lذ۴.8PUUvލÇ#;;6mƍM{8}\|1">>˖-'YfެLc"/3.7D薑ͮ rhTLdgg˗0LȐoϥDDDDsb"""Hrn&;6|k`bرc8s ~\5,>+V@TTýa{ P^^m۶ ;,466ؿ?~?{1رfYp^555A.^Q4dժU 9汩V&zI\ΫeDS m8p߁-wDDD3a1lsr0t^#lK)$[&400SNaӃaκu`X"G?Cl2QBZ瞃<;v`ժUJMQTTTqqqX|<eݺuHOOWzt\O(3L&iu*s(є]+˅+%?8;;{Ap%b.]Ჲ2\rZ8557nĖ-[Bw 'rx7k.TVV;w}P DWx9adiknnp}D DOOOc/jfON׉hzp8Zrgݎ6'$O@tytL&nFddd,. {RtttXuMNNΤp\\{BU|Wr᭷}|(++֭[M0,ZEE}Y9rزe 6l؀hQ&vFwމK x-'ǎ(l29@gg'4 /_49fҥJ"DSSSحc#oyyy͕/R'+ \O9tt=p,DpǙyyy0h4 a1EѰkQ|ut:Q__/Wn6&%%aɒ%XtiPJQQa׋sɕuUUUZFQQ]|^ïkٳo6nl޼>([_t_3@뜦ktthnn ؈.cFO -33S=!<(L&v!"a1)':4V+ωBRRR*`2'' YA]]._4 /^,eeeXd Ӥkt:a&LlhhK0<< `;O`-^ Qdx ˅D\R>4#%_m]h½˃bZVxO"E{{;ZZZ5[ZZ&_oooeA)lX ł4"b""":. Snmm9QQQaAֵshhh@mm-jkkqeyZZ łBHu4SB[JaEff&VX!KeXq}~?}]޽GA~~>lقo|&% g^cc#8Î;`6ڼ:Ya'디?(ib CCCNAraa!@90DZhiiAss3vhb!lFnn.f3hacXLDDD흴 X ǤRp66:… gK &&F=dhhgϞ+{=ttt ,Ç555?[;Ϟ:t=xرVRzhT},>vFGGoY}#%%E<6UUr`!V% tۍ7|vBee%˱sNNE^555A!yyy򄟵kעtCHV 02*`Bt'q:I<4Tn"a1\'|xɖ' /\>H>a4'T/]A='O>BYYV^իWc͚50Jwު={ol6cƍxǑ7VEE}٠66lDr1228,_\zj{I*>Ufmvhتd S*$"x "5-?^ F#f3F#uAMM .]Z477CEjb())ŋF >vΜ9/W K+p5˅_صk>cc۶m?ϓj bXZݻDBB6oތ-[ 55U->.]>TTTŋE!hɁn :N!rf566U%Kk(((`kt:5[ZZ҂&j0MX,ץK^-X t:aVK!HNN6p &>|BjѢEϟ0&%Hk K'O<.hZ,[L>n:X,`v8p/"}Fr8طox衇OD-H dۋXr-$;JH6Dnhhj3228pGRR{A4wM꺮cf9b͆vM`R"U +wB \]]-h40MV \3PUUݻwHMMWlݺ" t:gAmm-֯_m۶{Qzh Z`1\xuuu5FGGL`=?+IĎ&8NcLrss"9a1ՒZCʭ[[[ۭ+6X,rKh~E-x".^bxx6gERT5,j;DNN]n7|M?ɓ'QVV 6_2|G(őȑ#سg~mbxGخ=B ܹsr'N{ߣ;JhJ^---a/bdd:yyyQD3Z,]/ݽa1Q ͆V477o` #?>&&r Ʉl,ZH="`P |ETWW^W^KApZZ'l6D|UUN:õ#TGG:^xg?˪9aq ƦMqF@. p-Q5hHC '?v|=1,&"F::3SZ-RSSî,6 -acMhhV $.9ۘ"0Μ9#W OBgg'4 /_*v߿z=/cǎ0Jaqkllā~~=vBee%˱sN瘑|rG jEEEA,Y[7 f9lhNID3gtt hllDcc|]k4䠠Xx1 PXXVM b"" n766B%"8??k1>Q]]-_vC䠨K.Eqq1JJJPRRdO4-W溍sOmm-݋_~* ?0z)+=4 綊 <8relذ_(&O:N$&&bʕ䪵k"))I͸c夤 #塨qqq )PWWZաXł  999H&H°fBkk+ZZZ҂f9N 0 0A[NNa2ѵhll _x. * 馛...,yS<Ο? ;v >ߏw}wƑ#G7nDya M8x yflٲ'RruuuPBR)=dY300:ˡl6crPUXXB!??:N ?N'MK ]+8B>@Vl=l ={V^7`0^d JKKOsRhd-jxÇŋ(//Ƕmryap8o>C=~%%%Jfnӧǎ(l25k8YX kkkaZjZ~\xl6} C}}'[|ɒ%(**bE2DDte-е& ÙLPܹs>B˗#>>^]jaЀE|M7ݤh1,N'^u<3m6s=JfEMMMP/BEʰj*+I jZ\ İhBss3"nnnFss3l6\`2`6a2#6LX,ǬVkPhrpp`4tR9QRRGNt}&ʕ+o瑊 ٳoӱa\t)hRT}l0\z^!)N B+1:: `10墢")DKOO._:\|Y^WWcz充(((@QQL&ka1\#WKfffbb ''gu-,^uuu8<>#y&㦛n7ߌe˖ד:ьikkÉ'pq8q~>` IDAT!n7ӱzjYk֬+<ގc޽ߏo~XzCQ>&ٴirrr|', 9$FClϞ=ضm۴{,400C瞃<;v`ժUJ ǎÙ3ga0XQQQJ(8N\|EMM .]ZPTTŋEEEm.B4N]B+g[kya1Ҥ7gẺ:ˏ as"pݸ|rP(|i~`(|뭷ʠGXl|bwڵHJJRztnxqqbӦMxGH!0S>.99mŮ]PYYrܹw5.p8wܺɓF{n"dm/\ `b`jdzu)`B#͎znc拋Q\\,W!{QdcXLDtFGGönhh@MM Zr[d2A*7ܹsϖtJ.++cK pY'ݍXr- O}SHKKSzgb߾}xנ|7aXEW^y6l4,Ǐc͚57pTijkkw^7!!)St*i=sL_ K͛GEuV+Ǐo'v%D,h_*i@2f`ˆM8v{ic,UPPn… )..s#Ν;իWSQQѩyǤI=\A /?NEE^x!wDQA+׿ܕdbGidXvꫯ_~Yv-GAQ%ĉ@08;ٻw/eee;v,2ǡDIk RVV˲2Ñ#GPUEQ(,,d̙+k  ,#9r3LǎeS+#߿]`,C>3ϏQUUŖ-[vh"oz'O,s 7p}~~kq2y׮]̚5+ fE7[s} ∎mۆ"==ٳgm%K q)'޽{ٷoNoqD`1wU}v~*z BvSYYe@xKtl~ C_0X*++eupYY<w `$ieee{r7z||{3X0Yr%.,]K nQFQ&EEEEiTxc֭\W^yqqwpϽѣ#0RA<*l'ɓ'+s9̟?_>ڵkdff2eʔNg̘!ڐ C=z4(;$Ibĉ̜9Yf+g-Z " >4'Np!>̡C9r$I˾7N!0 <ȶmغu+۶m48 7o^yD\ss͚5477Ɯ9ss9GF8/ˬZn_~v;/2O<{aɒ%{|_:^0$|d_Q 8r?x-²e˘0aBǗS\\ĉYzu NbׯtܹsYgEnnn+ ;esײ2N8@bb"EEEYϜ9ӧc٢|ݻwo>vLx<ȲLaaapY(`" t;=z]?RтH` 3֯_OCCCY>z…,X@,#ۃN 6P__OJJ /F b*."wyO?+V TUeϞ={ ܹCrr2s ^`vة6@|mI7#CjC%7ppC^׳~z6lmz=>%KpgpBd߆6+}aꑎ(E /&Gg}ƥ^2P ~;c۷/£ 0sWnu&3 '3uz>_t%Ik$S_ % >,wyg%OGdYjo/~1£ ȓntmr$Eۈ$u+{pvoߎiǡ;'77 ϋR=gYG2וiL{Ҵ%cѦi|` ݻzQ%؃N;-}+?}'\pa=Gbʭ)ϦO0NA~ ۖ6şxcMyI޽]vs`od׋je̚5s2{l,X1c\s EEEg?_ "X T?jJj`tЫ,h&'K$d @R j"EE &/VE&KXeyD) 6;n)jll8p?p@ஂR!hKqkMǫxuG7:֌΁p`ʓE)2 D"aeleDLۙڵu]k5~M6i&mۆ 55y``pXjA3 ܪG UO]yna-$I tMs*_2V9GD/r;7JJJ[Yl94={w[#G( g/^ L«oHM Y :Hn 'a0}t>m+p5 <y`@LYj:s$B\rwddL0bۃիWSSSlcU}w/b:{/> at7d=U'W0BX GSf,P_/ >ۉo>fϞ͜9s5kg QǎcҤI|_fʕê<d hN='yV$Qd ygʪ& 0&wѣGn\Y`Eu233immeԩ⋜}aA,/Z u:]TCv7pC }$=@ (*s`i _o8pG,__W~_ɓ'cu`ͤImY(8|.Mǩ8}:nM2;  )zA `Q$ 2bI$[dR- )V%ꫯrp8*nj3AII 6lPN;3<35nMǥ+`|5ڦ:.\Rc%ymX$B'AH$(2I)  I[ .Z[[SO=s=w8K _RSS;wnw9琕ˈ ̅3tܪ'O y3cov?F VY&ADE!b&`dK/r a1X,\ve,A b!Zʣ4#XlA@?RPrrFt fFg%"cS&e\%Ɋ;W\ѧc%ɿ3[nߨs:.U :>zu!pjfZ% EyLbK($[2#$TVVʭ[e<GO>^xq}U裏zO"++G}o9җ#! LAwv{5_7;}r}H6wOvmEd,ИX_mz \fyΜ9dff<:\s 2?˗/k~vlRw@7zujfz'=?6iiEY)O$krI9cڙ&JKKٶm۶mrt]'+++<>38;vlc͛ەmc=+_}bhsBSݟwY:̃3b8o8"ER@,E  'H@G}{ r$q饗;}?Nyy9eeeݻ7}ee%W'NdڴiL6ӧ8q.-^ݧӱ{5Z_(Xw S~^Y$dBͯl 6,\TWWswoO>w-h9z,T`Htq`$I_.RLϪj;#ͻ˷-jkkzLkײyf^/vyXH]JՌ>ЂAa `?3P ՛)/R2)4B"rJx/EAQ$IBu|>,sСaCa8t#LǛCz:By$̵hYjlI YX6^WA .իWcZ\qA$~ͬiMqZ;䇵=Cf$wU~H$) V9t"vluV>3;(+]=sL.>YFu/3DD Z01Х|]' 0FvzCj<%Ivy?p$gzj[BΜ9*p<=vrrrxGK}!RfT`gάv(VP7k€W,L&vh{v;;vmƞ={4sgsYgqYg``u'xe˖kdX=z4/_|qX+mĊ}9b0BvX`c0$0©>\eV$$[ $%`hZ[^tOCuٯoTTTСC`}eeZ[[aƌSTTDQQӦMcaO AW٫h8U$`5>3Lb$2,d$[͟)5X^{5.]n7(\r%\{YS^^,̚5s=ŋs2~A%^0 h ZYGũ9eh 1J YHȤ_U&ݪlU:ue˖/g&$Q]]Mnn.QVV [Çw* xy獸`[iktLiەfu+N՟8oh=WSz$!~l #͛Y~=7ndƍՑ 8묳زe ׯ`h2mB' IDATYr%H f&Oիcx4C0Xd #KRal72e09$#t]w$6$g}CQ^^,!m8q"EEE̜9"f̘̙35jn@t< :JO0=B,I瓁UJ`!;BfBߝ%No筷j#.9s^tE2r |KGɫ1](c&!iDH*P ॗ^{rur먭NVV/f,YEuYo4y5Z<*n-Dn:!ZԟbOzR3H"#;lRcULPȴPaH#Lhi4{TB% % _ل\F U78QxU5NzYpޅFvK@U!'ѿ2ɂql۶7x+Vz(L0 /yzkdix۪Ƙ(9;X$P?s=̎;ؾ}{kϞ=x^5kVM,Zηm~_>hgx>Uإq"KF[ "I |?%8p 6iӦV }Y¿~ʿF3XZbxt`b-c 2TG}}Ǿ}޽LP„ ^͋JX@3 *5N..տ%3,R+eI";B~$KQַp:]:}vΝ;Q񯇙{u&Ղ &A*˓fjW(˖t*%,n)Ωw!W7F'[O2*J*.];nP, ?8wuW aP$E&/ٟ5**]u.jJ p`>6Ovrz1 +mBgW(ełiP0 hjj1FO$@AX8\)[%*2B*j۶mfuۇ<s[ZZًs חNl_AЀfJOH0HTdd'XdZ|IFfFzκ2|h¿tm`$FXl#7),n6/#0@z9y Gq|/pxnꫫ0PbۅA%I_~9oVFtGEF1BRy%}@c)hZەeyx̤!nKԸ|T:Ը|t'茡+EQU|k֬s P#FWKÇCՐ@tC` LA1)6-#ߜJպ|4]DgU&/J~e^N8Ժ;9$cjDa- cSOM :<=DNN#299(? 3qO 3hY \"Eo%YHaK.>SjeXU0S7ec&?IE3͈7&[U!?d+Y chFEQ|fØ1c/ 5FGxNK݋SDb`@ƦȌO16edrk:NUNunNkX i$r$[K2-^ ^|*N`&;e%Z+J&SuRJBnRVl6 !&"1/:"ڧU&Mh&O!k$RL$ob!yc>A8e ɋuuu|{#??1cƐرcϧt_'g$ldN)Fo،p\]XDZ-ljeT*?fLZr*hj7vzEfœ6EfZF"z~5-zvQFP (lQ(LuP{cǎQYYɓ'7nv[otN:iUE`H1?M0,R^q& .0wc@vi $[#0@V  SlLH$y\tt*^Nڽ~\`81+˜dcR$ ҸnL:)SbΣ&XECȺ 601$QbeBjى1աu$ [>N:؅!()*D~H~;S>rxŮM(Ld|j|=*[";&f Wߣ#Ab}r.'vap`;X]ȦKAiyu Sr٫@&4Q*+¸T)6lOCEMb'4xRqLuׇ.Xt*ZT=T94!$%$I"/¤r@D]TzD pJTd$dzέEWN@A1yIVƑRuʚ\41Bp,ՔhefvȎG38hU7]00e4Q4[ܔ5-7 )/ʬ$R(I7k%+^HIV&|{H[ez$ .1}mLd-_t,6:#j>$cN +QT1UΉ0`tAa:Fƥژs%C[{]Ժ|!:r@^ƦژDR,n tD)lƤ(a1q:ZkXk F VIbrF"]Fމ]DibJLIOdZFbLWj4{zB@ ӯbQHu(or'l(3  7_XX3 z9($zQd@Ś2 NNڽB'd$Ybanl2Vu=.z$/735#IĒoh}.4C PSsm=aCu+.(` ŧ5>v_$zb^YC1%q$oKd;S.23:_k10: l31WbEqH3Y|,ܻgWČ N*Z9p$Vs}XkjZH ў/=+B/}g1M'[tx\c XpF7;J'rj[!7drHt\1hjl|ؖpbp묁pУR\<*p)# Ŷ:GD-v;nĢb0 M5vj"ϱݚ?PlWEƨn{8.݇ݍ b+]^kW-c'F`Wvjv}%V8RbiKց/20be>GX4ETu +@X,sE;2.PO9~膁ld$]XnIn-}$V`' 4P/tTq@<̣HKA$_0gGSD 4_m[kn$#IFҵ?`92ٍhxJMUp@_k|`9y5Z Xx:_,c`5I¿?"5?_ﲵ7vUMc[ˑ fy}WK3VW.3P55eiX RZwjfM^n2;,g^/{XdXܺO_\~AgKlp1px?TO3 J׫9ܱgȯ=߿|uSn_{NhzVˡbUpJVLiI׼MV?ދ3s\'b]-tqqd4oO?ߜ4${m^T'f ;gp=g8qd{yMRWJ'8ggFInپ 2`YhC]aVߏ;|'ܴ/tzD8V1'Y_NnL 2.!7ݴ4 -|9`HVɉx w<+8+ll)D._:c^Y׮=WNE9C6~.֤s\C#aպ}82gq-q" so,j/ Octrx4y4Ql&oǴ?i _n~|K1?쨁ܟ(Tn]e\jqٍ/\Bt{⟿Ԓu/{4.[+OԻ,v{>*{A_+e:zYIY4 hiOV5h'!i=1wX|1&Z=LL 7y4*"֤O٠[T߿M> ^G oG 'aXki=*mѮ0 bL%<-nt#j Y_/"o~`({^[yq9l3]4 ph'^Ƈ%_,6 8P1^ u/^y#`oߋ~Y >c5FuY̊[{/eg#!~x%?/د\ɅSYt۵z=jrrS?C?yn'?=!g6{|י9.I us R8s~Jt(NlaqȘΚ/?'ŋ{ _כrFu<=ɴVucl[n卪/7:YV@[?d@ʨ9]_+[w3apeRZBX޿g<Ԟeh`VTQX}gMF[h/1&L./GkhJ\Ï<٣uX [E,r34rsekt6f|?pɌ`Q';j'6aIgPpk.!G}8t>'TVƿ˧?_{eweEqbTֳLgam8ؔP$ 1X\VѢPOe=G{C㴭-0 [݃$DFw :l0/a~!a>_ŠOu?`mh8Nm0~C3^bC'{OrIh}jsb0Ҁ㭞=6cI;yWKRX&scg8bLd_eN9|򭗩}e#}mm|T1k Dv3_Ӽy<,:ׁۥcxXxuXy|ǒe@"F)PN1~*ڀ/gMݜ`L/yxbmoN[.cnHz cXazf0p[#г!y( dL.Xz-ˮb=Pat IDAT)P#;;"泝-Օ3/%KȺl'Lk~|?kgcNi|΋Y=o$kX2e@O'C8<}DH9~sۿ$ݴ8Y)J~MU(E};5"Oam(Ů|4zçZD q1§ݑuV!87AW? db(l{UUڍ{'/\˟8xEB7ѣC> %d-Z{c{8griMaԿ¿<Kοi857) %dP4OOl#!_py唬DLdxK3D.n`b/_ʿ]gJ X"}.G'eS|W2g?^CݰM/D pvI]3?.CR[$rbddcFĝ]2o(c)dϜ`rol3I5_$2ξ)JY S64}YoL@0혰 Nn6?lx;X(dL+0ܟCH?@qmYOm7YQv/;bX>M$@c@:7hlw#^eQyø |ptو`dbHlا{єq yvEã'QBOY>^AAyq"lc'qot_IHF1\hewt[&t'n`$:`aOxn߼RM@G\߾ |Mnb vbHbٍσx` CbEP{l8p9 ЎQy+7Jj^JK'I1ʣT@!U?KT7Ę$& rLlYN\xv0ޟhПJ$r9t9zGЉl&e<7YR_Ʈ|b尅s9j q)VIon/HX B†Mi3o[+$ R-3>ybRH8  s֜οi%*2~L!o9S3Vu;[յe~hZ, Y=سRoշ<yڵ,}u#kgI:F_w0'vɶ XB{{:>3et`LVd'Z<FA2fYaW 3##d>[f0Ȇ߿Ec\Nb99H6~t֫NP)^xg47bϻ)Rz+I`TRQBn_n?%qXv{!&ϭ!zp'H@ne*Kq!rc~*?7.U;5U;d郎gPPd ?9|`XT)r2ׇRŠ:>zw {\0m|r{4~mn1 ?L~=,n O zwK/ګ{~NtI˥,I版9bI]׻S(Ί=] gD <}G?_~_t'E_޴dZ를ٳyc3P)dD oKX9Zs,,F#_}-HLZ$c'd.vx e/忻]9 U50cD8^륳tCUO$Orb.W]G>.EW :gH@¨ mOPBWr(Ҭ ap@jPts癍|&FG `Be`J0#YzKga\[WBٗeI]8*Єћ,3>#0Ajh,LeaSHDq>#zPÀq)USPb[ݞ.}~_F_q\jO {\uɖ>^p# 4֪qkv$IZכFoSֻv^Ovxl>}N Ɓ\AaZr¿yawaU4yb+?ۏiqHʛ¸$$xf_4ּǦ{p\ ? )]D{Kضz=G9Em3s \&Y8^Z®O>`_Mo9 2z K; Z9kW9S|0NWkGVta)u2&=[+9['1u5w_ػRr󙜼SWm0{od1哭p<)njton=va d.ٸ“$HgNæobU8i)2(cȵu/CspϵKTfc|фC+h̞pMI-Nz7EI[kUƩRI1eccY?Au]>Z񣛙,!̯^̨Oߡl>{=;4 n i>|I3a{{h՛pc<Ɓ??ݐ^)d79zGg,-Y}/^xp'6&qc%tà;:#FMoLm= V=:񳙔ZƆwg;Rkd;_loG^9g@'Z6,A^)u]*n-k.!G2x'8N%O&fJWaS){>^KC<\r- =QKkw+ӕ}[ٱ23II 0bS$N9#[n>Gf||)ؑ H(I#EHT9}D{w;FlLsrƴ4Ʒwݍw +8bNf|VFQyObAׅ"Ig䥄D|Mƥ MBgWyܒxIvl6ho[(ش0R`\-Ɋ¿yanat`jT9բgo'븒;_`Ix7G_ѩd|o(.a?OX1,A9 }UF>10g>5uTЪtcVh$I$Ǧ{fǿ1r40}$8?-lZCxNr*װCN#;1kMnʛ\:) c5Ye+o̖#93g+v`6r4Hlʖi/ KG8a+dY4)J1^jEfvz 1 a:'-8a|5 =v{7Y }3JM|r(Yg!De[|.Wܵ?k"I|xV0zj8^,S̿V+&ى ('5!ŀ 6|_<"Q j'!F<yzy]1!3D `=^k.b^?d|o1D1|{_ jUPV01͙*K|t}E[lz>ٚˊ亹:k>~G/᭙vi>EⓃEc "ar\w!p^*[u`vO[6 DuϯO>gՏB0y @͓6 Y^݄1fe||x;8/;kg=r碃vt93v3ۍR 帰#Vz~vTQ7BD/6H۸~]B {͙:Oo6O['`5RNSwO <[ p3ۇ2_4;_ ~Oz`)~@8l>k᭵ V$}<(#vG~x5yG×?ȐV*"x?_+Ds: b SߍU7F4oTBN-v%JyOW9'ir)~s0@7B%YlG>`1d)||8^B-k;e'y>Higa6Yr{nKiO)~c23\n;w69f}gKo7CV+ݹΡ4pwGC$JL%i,N\{arI}b~1^ؑW BhJw[+^ELWݘd*@IN{`KhO||8@kpv{[u^9>>8QPCFvU/Avo (8fkh-`렴;1~Z#JYK6BH/U;W' [r6BkKI|Jk|MiȔT5mpXuj_=Y\* 1327\xxg\+.AubS&\B@Yy{;jَ8qILkv쓥RF;7%: 2u]C4v4]Li|Ku/ %BB @hFhUQm0 d_ĸMI8 Tdkn4ʜIA_dq^q?^$UL1H$b8&9[ቪyO'iNwHa{mĪRE0#`> 9Yw V;V_t%i/c<8Ii7df,8xO`>OS"UB V#Vzu Ǹߍe;r1lpQb)<'$qj.Ȫa)_$W'1tMQ#IYqđgﶪWX:F}}v"L aLiV2 Va`c`I/<%cVB'֪9`"ȕqqRf+fxpb:O:q6:r{xVtSd+<%xOq8l)'N,B`V#XJ0>n/ zYNL<n5kG^iR) ZvAy5j+ Jk"I` Aj*UGd~jčFzp Wqъ_v( ne ýdz,]E4aGNSU/Ev4{I(%nK *Ix}MyB.m%R| E;"B$!pF=^=(ڑ뤓xPrq]ĭ5mEձȵ00Ó~T1#=mׂʎx1tRBfD1y7&woחOO4pd839^ 3Ĺ@λ,V j>6"5.(x3 }$N w!66;Zڏapyn:'*l)8NrvWR__樯/>Y<8W8sQbFWv7olRb=jD3Bta#hW1]\Q/'IE%|lKqv $dzIrj"`i#}ܨ2ɽ |6H"U]pS( (yҌs݉Ẹ^N6yN* 7횏&.|3VŤ"6NM;rf#D>B$Jp 6~P+_DLΓs[5R2LiĦ E[®s8ݔ>fftcAz9g $q8Jr ܶӏJ0hEH1yhB[Q`+Lq0Yǎ-b2xثz/NyIr'9b@4J!s2y(%#C̀"Sq$Ap#Uf(?" ŭ']ƬujrR}:i(0vm&Bm^ٟ,''%ڡV GaZSqmw=m+9n*( tS=&k R^nfdnnG>Z0Ȕ:O@)?E"%`ذE)@p| \,hZ % 1-[x؈<$Eَ;m-)a?sHlG&1] E2(|koI$Y fa-߅B\¡-F]͔RŎay둏'nF߯닣z1zI'iAOuHۺ*d,MT(6fK4jRL]G)G_I1 =MgwmϜTi'9:K nyoϐ)SD48wY3b1h8>J2c;B^#h6mi:JO'vS'HqIad.)LA)IQ%a`t\9rwfNq`ļ-{vp|3^Z-01dXL9x<g3K 7@2T1Gq^6.ۄ,&g_4IDATBm q1RA}}y\a>r^j^H`C,s3< #cb<5OK4}]5OR\28V]F GzRΑȌ[_zYn z zw8Ϻ<:\z荊Z/yqƂa(tq>LySW MRzhYQTmq[4Ԩ@(ny6 R? $F )W~`n'u#.`ԫv6 Ib-֕GiW{5esMU1.Íak/Ǜ@`V09Q[~blHMU!Qz?r w97 Z\P^_%W3Y*6 q_z=5QEf\±(r 健k;Gh@$M"HR R"#_",Eْ*M.VtwȹU*OtULjB0 4}X ֘*|tXJru*p+& &A捅"rhh')$,  Gq`KݓoWW;EOF{>0r%@LaD7RIt\=}D 4B̝}y>/s88~pVh⿦G`Eɵ ;2,e>W|uUFx_'QI5 &\ƆTM%sq/0hqX0IơBCA>υי,*HrD)Jާj8wG#U >z|3i@!nS^C\' 

cc` 9db4 _ PK.Y6C.KN-fbáD$y2.(G``i@5O x0-N/ X}ȬQIL!.ģMX$s[0UcY΂9AOS&5Cּq\JK%+ŋBk`R+Lkd~hLf6 `t5A8Z6ف `;ǻ h=!FFz =g7mM8bldg5:&7v(PU{VlZ|UYB(cWf,F"k["MɰQ1r:]"QJ+dv8pҮ&cΖ|͸}PLNM痜*ji&r~pJO/Gf0Ʒi|c'UHoŘԽvvjWgǙ6y`gn'緢tW8uOơAb,^j,&B!B!B!S΍#B!B!B!bB!B!B!BYA,&B!B!B!&!B!B!B!̕GH 3>IENDB`doit-0.36.0/doc/_static/requests.models.png000066400000000000000000001470111423054503100205760ustar00rootroot00000000000000PNG  IHDR{}bKGD IDATxw|[?Cvd/ JJ!hC74-붴ے$$@B( &v3 Iֲ$YvxcY?G$K~\. DDDDDDDDDDDDDD4zDDDDDDDDDDDDDD4A Ơ@@DDDDDD4ZZZ`ֆNXVur<. DTTTX#22rpvh1,&"""""anl6CףMMMhllDSS.]]]9""B xܖdHIIL&?sy2MDDDDDD4D:;;Q]]TWWh4B׋p]]fmBBB<W5<< EXXh!44 ??? |mmml6455fNP;v8 @rr2r9RSS!ɐ4###* !!!o@DDDDDDcDDDDDD4P^^2TVVpMMGT( 1$SRRdK7 FQ M&FIӉm!ˑ!\DDDHGDDDDDD4B0,&"""""܌cǎOFEE`2\+dsa\`VCCjjj}Zs Xhn̟?AAAR/b"""""{_:;;1e,\ .Č3 2IV{n|'8N7x#-Z "--M%0tY޽}#66wq̙3R/.b"""""?Cgg'Mh4R/hhmmŲePTTvGQQK17p׿BNN~_nK<""""""™DDDDDD#dttt`˖-;'`۱m6gy| d2ˢj: $.'ߦb7^exPu^Lnn.v7*Q6.Ca Gwq'0u O)9g~/?g~%~jLxppv+Y_=]pNgk'^~ V̸ {~_X?x}W!0m4L2|iADDDDD4Ʊh9qf͚%Vb0>.\{2 >~:~~~OwJob{U>t3y'΄:Wuu~~O7@{.{ ^^)j0w{}?)!8߿5S͜W] Ud&1T8,x瑵j, …ݓ݀_=~>#k_ .>"gǥ]ưhiooGttt & CxU278pr;Xƍehw-z30.#Ϻd=€yX>Lhá?.¤{j{}_z A0gK3Z]GWMp!!A.}zOG *#0`sĠMEa FLN'24}~NF7խ}Oο̻^Águz'󆙸=r.d=x|?:Q_NՁ>K|{?j"2DDDDDD#ܹsSex @zN6BZ>`sox(~^1g?=TK=%~/=eHth_nW6_}毗#h* ?=.Bm@v߿_7g?b駟"==ǏGUU, %[XӫS _w,Xc̙CCW,ǒe^>9)c5x/?v~[ܑmi@6r>K\uk#j[=!> X57bOԙH E\Pft"l8 ,|1OXMkh&w.'\p{?ގV-!q;~ ex6ei! ;7hr\侾 &&{??x饗K/~hh(>v||l ܈^wb 1sȘ{#h VЃw{& aG1)w-CM;Bb/W_J"o=;Mciȱx`о}c{3&ğf{ol]\ މH??ዝ b{x?U= ?xh>&mk]X#oG>S F?8VX]]]hooGKK ى&tuu @WWZ[[ֆ:Mh#<<GXXbccpDEE!** ለ@LLx8q;&&f5"""""r(""""""b555(,,_;wNűw⯸m~wH  6"""b@hkkC[[:;;3m0DEE!::aaa@ll,44刋CDD"## Պv Ff8N#00/BÑ /b{Lzq4\.lقu!55?ORT( IC [ZZĊgfzm| 7Lv?Alll F%9}'g0o\\RSS???88Nl6466l6CC`0j?+:: HMMR\.e2}Yl۶ vy?~2^8G}Baa!Q[[ &^/11Q4T*1LV(pOӉ455UB{m_kmmEssϟ(}B,lXLDDDD40X CKK ğg4QUU V ˑLq[8Bll%g}uٳXb~_!))i@GKqy>0yw48VپNMMM}~O/¬游^U© =@DDDD4!all$''a{"n'&&aQww7jkkQ[[ V N'~`4QWW'd2yDAP **jH;oضmyF?ڵk1gIEG}͛7/ĤI#`ҥCvww`0&+L@xxWžf_r\\~YLL=%aXLDDDDDjggmNw}mll`0P[[ F^/n {"jZ  8#X&Dݎ6mK⮻Bzy4Ǝ;h=,X???硻[jN'v0Lb燔( q~RBRJ\.QtyGw؈&466 /u;((H{DDDDDty¶|^9 /455ٞ ƣPMWEZF@@wR;p^u{hiiq]waŗ<6]vaǎ8z( .,[ &Lzy߉d& h4L&9% )))PP(P(B唔aoFg]f\DDDDt1 Bz෩\^_/kaMBkZ8o |Wصk<:`ƌ[`ILFN>?}???㦛nqUW@A*_~g(_⽢ᢳgmZZZz^ )))kJf"""1a1h! 3`2톆ۆ뾝 \$$%%]Q{ok4tttf_Kff&J%?q1 BAAf̘|gYYYXp!nF̙36ٌ={O?'|Bo0 8r= Պ̘1ӧO̙31m4DGGKl&|m26Bm nhhO~VtttArbbϠ944T&"""hFccE? 477{Vhay)SRRS[kk+Z\`V+stm6<怺sCBBx=cIUUQZZR>|6 rEEE(**œ9s0{l}OO8ݻwO>'|̛7Ü9s,`h4سg݋{>}:nf|͘Ni@\. ebyn9!!!*+$&&"<<\&""" 6Uf8Nv}UT*DEE z=4 4 jjj*a\ \.Gzz:T*jx.l*ᒒ;v NSBzځÑ#GPZZc„ (**BAA&MTAY]\OOQ^^GEii) B67oM(//\.aڴi5fz:ZV tvlSRR.)))I½ѮU:U5.a1ѥ>tpW\WWvZP zQtmRJ IDATi_ZVl- Bk>P AڊC}؈L2E ϟZ-Z5XZZZǏl6Ñ<"''ǏGFFƠcQKK qYGVjjEEEd?455y3g͛':G1@8GM&x01@Be R R@{, ݻ.QDDDD#b""""WZ'_Rpjj{ Ԉ,̄ZF@@}AEEŨضm^y?!r9PQQV -hJJ ӑ!ӑ4'F}}=f3Z-SMM @ff&rssgl6?ēO>UO:3fCmmmϜ9vqqq>_3XIRFcc%& }w)3e"""WzZ ~/pa^Jv@(ClLRw?ݾ"b!׋3z=P[[::vCL1Dmmm۱afvmXnO.n_6GCCCsEFFU|(JIGDtwwbbl6_[,9$$&&"99e\L$r$'' G hd^lhlF]]Yc>c`` RRRd"992 !`aX h4/*ǏyÀ0UJKKŪL13g FTunwl޼%%%(((ʕ+t+0\/< L&F#F#L&bx#**  All,F```Ippp๵գ~8zzzzU1bˆ_ .(`&άV(L^a]__*** HJJBrr$QDDD4D|؝P_<99'VQUYSS#5Lb&ΉGT*ddd@R!55u}`0x-))AWWqUWa֬Y$?8JY,ضmz=.\kbCQ}3mjjfCntuuy쮮.tww{\&"##h#::aaa Ell,(Jhn~z޽Xz5/_>Cv?~\|}WX, ='J1d2vRH#jECC'fY;vrcNxCDDDb""""x===>Z -VCBB ɐQaLY===Dݷz!`tt8UV %ݎ'OŨF@@&LQ阛;}9~8^|E[ ½ދGyjZ k999x^ʀ:un݊7|Xj|A$&&Ja0<*> ͆\}r֬Yf:::hhjjhjHKKJBzz:JxPZ)] <:Ue"""a1]:CFhZ8vޕ}g{l,//Ggg' l Ghꨌ V0BհD9rV111:u m<\9N޽_|!V.[lR5,fغu+۱xb<͕ziǎ+F󠎼<>/s}OՖ33335jilδX,0LmCBB𣴴ݽ3gΈz ]:555F8/vP# ,ۋU-t:]|Qf2`0v8FlGN+Bee}+ѩֵrEEEՇ#yŜ??<^u%KvZI7VbPE~zk׮Œ%KFswKRyT  DF0 ^gurMMxPd_]BF ł:l6cz8Zd2[(L&Q(d1s°h$r~뼶6MMHH}g\UUZq[_a0?888X=ֆ'NP__ UEEE8qX~=vލ ,_˗/Uc-,vWZZ͛7cΝP(Xr%VX1F9rD@.))AWW/<2k,$$$H\ 6 )L +T(JIgDDDcb"""{Nшa' 6qr||h_u J:x,acIUUX1XRRcǎtUBsW#44TnӧOcXv-nvH<ɍXP]]M6^?~[*J 9Á3gxt (//BffA&ӧOg1JɾFV0P7&cw}JBTT{GDD4*0,&"""ld۹ #FQ2 b۶Tp hb:X쉏3 V*#J.Okk+:$9Ccc#QPP 9χZz0 xWϣ/ƣ>|60,0zÆ 0͸۰n:L>]Il6СCbb477#22'OF?>^. !L${!!!HKK$ ]Nƒ.~/ KLLDJJ{T( JY&=Q]) ^j`0z ~!99yLT%555y|ziyTAT T[TӃ bXbpQQN,)‰Xb|A$&&Jaaqo6 v† p)̞==-Z I9.NWjp8݃d4YVqrM&xd2(J1@NKKp9,,L=#"" b""""_|vo9h0`6Voyn8<*++*XV@dd$SvvT*& h5\$;,l6|k֬w=&H.}uvv6V^˗v/`޼yHOOz$1!Lh4FAuu09,, P癙 y6 h4o>}phCDD DDD4 ݫ:ϙ>0Hj^Y!~_.sۥ3g X]l67-[P__[o=f͚%F ŗɓضm|MDGGcժUXQ»+˥V&Ə9Dp&FTVVz [7kшǰFF ^u:x^WW.&** Bl,vdI.ZdMMXa+äI.e0Ċᒒ QQQÓYf!!!A[G/7|QQQOիWCTJa1xuVcx'+҆68qB<8GPPŃc0qDK\UWWCj5Bh4z'}*T*ADDb"""|~^[[18$$V1܌^,>}F*&L@dd{A#nɓ'=*몪 &xTj"N'vލ-[?ǔ)Sj*,]-W;vƍq9,\k׮ł ^ڈb0<:+>|6M>v.n0ٳhkkAT[i" ^/&Emm- t:L&G^3U*r9J%j5"""$+""#tܫFZCh4d2L&~JFZ Νq9;wgϞaX\0##?~<ƍldgg]j=FLL NR:..N---x7qF֊u]Ƕaw#İ~zk׮Œ%K:r:::p1tϞ=0 ศyyy|+_UFAOOϹާ[D"^ v "ޕT5X |9l6jkk=fj?(1q^M81|gΜ /W_}#==]ꥍ* Nii)6oތ;wBP`ʕXb9rVkqΝ3N }_UU%^Bth4jjth4h40HKKJJZRpE0,&""s)+ ZL.34 w(=C8//W(Ƕ4 .UY.rG,Ӊ_ؼy3vލ,<~aƦMk?֭[J%FŨfB ^_Up!R(>䬬,@t;xQYYfN&"`XLDDD8NL&qVAt:F֢KMxx8 :55Ul-ˡR X |zzzhpYTTTٳbhN' ƍOyHH{AI{{;?.bY5]ꥍ:GEWW1m49{وz4]jkd0+33< y(kZq~rPP=olHGDD4v] ,ZVl`2` E0 ٳgq?V?^ @`p\Syy9N'j5fΜӧcƌ(,,DppKU*++? /2,Y5k0B fî]a:u gc=ƃ!3g<:B233=fww$[\ 8e_=8XLD4j0,&""V+zv-<\ZͶ}Λ{ӧOh4-wI&!%%E=Ѯq8pMMM W_3f`̙1cre|dxfVIa*..{ndggcX|9G& ɄÇCgg'"##1ydkČ3$ri jhh@uu5P]]h^v'ZnGDYhֽ}T_aR䛴A 3g<6ƦTUU%Vرcp:a!:u*ۙ2Պwy>,ka͚5Xd ^ޘŰX'OĶmo"::Vƒ>D6f£}uyy9\.rȁiӦIb,TD F@`` T* 9++  p8ıU555jbhj@ZZ222|RSSUH: >C;vL>SSS6Պ O<)EVVX,&L`6 9aְ_~z!??_޼yHOOzch/[ z+# e6/b֭hooŋO 77WꥍI---8|AFMMM)Să͛L&rDW2+ AUUU8s |eDDa1`*j }y X.#%%3w]]] %V(x|3KR3 (:ޫjꫯFhhsJKKyfܹ fJ4rðxx;qF;w .ڵk`6WCTJ.//Ggg'o߫}7Сjp8~#h .Www7t:jkkKFAmm-GDD@RATBP3|ҠP(P(FB(++CEE***P^^ 1ЏCNN0ab„ `X\GG;&~XW_b 00'OΝ ;fl6|x~aO~~bX<8N޽ׯGII vZkFp `W HN'no B5dzzzƍCff&۴}v:gUrUUH.gff"''扈ǰȝnbU $88 oD+r9+L͆svp%*a> ƪa۷o֭[a4qM7*r`X<|W+ \+V@llK#/UUU?5u+,,L]2L& 3ZJ%=NƍCVV+# ^* ZͳCb""c|qv[}tÌo>NAAAP*ap^^&O(OU#ױcK// qbݺuPTR/.᯺6mk| s8~uuu ศ'NzDWDߏW $33&L@dd{A4uuuH,|F*F^kkkjEv燔^j)))ZhFNNrss#dJ.V5,T`͙3UÐ&w˖-1aZ <å^]&#Gkk+oߎ 6l6núu0}tFȑ#ZHIIW_-͚5ϥ4WBz}uǏ\ȥK AZZhưF͆^jϞ=6qqq>+tά! SN>'NGvƣifu҂7x=t:ZY-ưxlصk6l؀SNaxXa:;;qQ{FA@@&LѺ://4jya{q!::Z t:=f? BFFH"Vk4h4t:2(^744jZVT *3FӉ^pEE:::j9994irss1i$ &&F]KƑܹsغu?FaV\\cի|r>PAO|#8U%WWWC\:#;;ep°fC}}}Y1BUsģ2o9*r9&NFjʔ)HLLxDν*{lf(t: 7oݻx+V@llˣİxt8y$mۆ7|Xj|A]1v[ш]׼EV \xyfaXv-nv).f/"n݊v,^O<rss^ ш#G%%%BTT3g`3[[{HRJ4V+jjj<dv@RRxP a2?3#AgaX }U{ N'QVV Bh(WOteY+W^Agg'/^_4iKAưxt;qF;w .ڵk`Fp̙3r)Va鬬1j* cP(zDFFg V۫ӧqY8}f} ƺ.ԈsF8k%44TT*V=VT xh455yT ^Rx iĻԪYf!<<\)..Ɩ-[ 99˗/s$$$H4" G7Ӊݻwc())Aaa!֮]%K 00P iiiÇ𸸸͈ɓ#99YI{>ٳhkkpkXVVG'B.KDf 6O:. ~~~P(䬬,KD4|1,&"QSS#aFlZ Zt{2L=ى28pFAAAL0LD… HNNR3/ **e `***gO>Acc#x/ALLMc٪U퍿o:?wsscSBl۶ _~%B!z)[6ч466"))>NHH@mm- tIXZFII / KRB*DHooo1&!/pRrssҙ51h`b1`;uuu:=|[Y_ Ðp%Ta- GEE!00"3=ݻ0++ "| & 88`pلz) 0i>棏>š5kz'N`ƌzaHW_aͨn:;Ц1 DAAwGJJ 4 Gfmy.2333qU^\֚ Vf}X v731`b1`^@EἼ<466; ntD˗/7pDD,,, l9t ZYY!,,w2R۶mCBB"""j*,]6a ***Η뎡C%$(J߿7oFFF&L 6`Μ9,hԄ4>ŋX,Fxx8_s3JDnn.:::,XqY\QT(((@NNTWWg! ((,Q0`0Mee% QTTB b13!Ц1)SP] V\>-cqqq׿cǎWƊ+X G&T/tJ%}T*v)$6{xx$$1uuup-$BPy888g0 q444VD"atD`,K%mmm|贴4"== Cxx81rHLJe04---HIIᝲ'ɓ'2 Hjj*v؁o&&&x'vZxyy4k.X[X/ANzz:>޽x /Ц1 n;wL0b^8b/;D.C*"''G'1??* @^ D`` k7` LW;222PYY NuVҚ0X`0ӹ_vhe/OOO&3nvoaիWP(`bb???>3!** Fbep1c2 FcG̙3?˗nill#Jeׯ3AF;v?Fss3/^W^yAAA6ad2^<˗P(`cc1cĉakkkhs~GGG GTl䠦`ee@>~D"1+`0K:@VVhm9$$`L,f0 @opۼ7`≯j5oŢ-Jt^>woę8q"||| m.ÈhllW_}m۶SNK/2z<Ǐ=9LLL~zlڴ@1ݻ[lA^^+qϸ#:yPXXH F덜FX OOϛaeeehGcc#ups" BPP?/}X`0݉2 hkkݽÇn]Ԅ\a8%%D0f˅1(//˗u21cưr5l߾v@ %K/ ??FW䌌 9V1\E_Gdd$֬YG}Ub\16X[[#44?6(Jݔͷス}}}Y'Cps2''srr6xyy!88#GY&2q0` ]`DOOO\񁳳_ S\\`kkQF&&&63j5rrrtᬬ.3hX\.믿;vD3g?ıc+V`Ŋkv888E1UFrr2>C۷XjV\٣oƍXttt@*޴w___tull,"""X1ыܮ-z2}D9T~ڷ1= pRѬo0hGs=TC#GU;c _Hcc#t+ aaa0ANii)Nk׮!..&Ly{!33&L5kC1Wx'~T*7 ~m+(,,Ķm_B(⩧ºuU*  c \t>NHH@kk+,--ƍGGGC` 8ˑ Tg=nT D@@yߟEf0@W"2WZD` ab10ۑ(**BXXX_ SSS@p5\rɸr \:lHDFFbԨQ6 A(((x@Ѱ-J2e r9Ï?L;wbhiiŋ~zj@_ŬYYt]S߼y3*++1|_:۳gx BXXX3ff'-+.. "qTTYe#CO455r9ի(((ZD"?/Vq׈#ؼd0zZ\zBP(?CCC1rH` oUvpQQ4 [g{{{3W:g '&&/_,1iiiAJJ 抅F SLe)ɘ1cD(((N9W\/zΨ@ ѣdhRc a̙3ʂFH$ 9iӦtF?.] ⫫i6i$֎3* :YYYYz* b1<==o!C|c@<*T*H$bȑ ȑ# ///C`&3ޥ%%%]׮]CCC ֥ϲ2}v>w`KHH@mmmpdd$;152L_ޥKT*\ XagbP*P 'OL&Cjj*bbbR,ʛ عs'[l/hh"ɓ'̙3ݻѡ8(.o>,\@V2*MMMHKK/\\D0~7q^/c…jE"z2zyyy:"rzz:tY>v3%L,477CRmmmPhllKpdRwtY3$ ,--!`kk b`/a*ڵk7CPwwwgp>P<<:W011iCEkkkXXX`Ȑ!\=z6o|ϿHJJ gddC AXXNᐐvATU*ozn}}=:o;b1nz.wVVV011^K[zL$$$ 11W\R bbb0a **e# hjjRntGn>ߴOD|eKKKFyg,[ DķH$§~+Vu^@[[ 7?V;x ݟuHD:fggW( 88}@ @ _~e˖qTW7 71[[[aȐ!Q0z>X099IIIPT|v:r9/FW1pXP(:WWs kq[?FߠR,977* ">>>|Ր+?2e^p󗛗 h4]π@~{n3!C]O"##J#F@hh(H .D@@xeu⾠؈fwtt @׎vcHr 7Q`aa XZZv5C~P8X~=VZKu:!//㼼2)) < ֭[wObZLp||< ! Ǐ,FAUU*++QQQ|SSS/Tn n_vnnnprrս۱f͚i Jzc>^ hllЀzJB]]Ƞs3( afff5lll`kk [[[{spp``OcϞ= ;۶mK/ԫWVV娩Amm-UqMM ZZZΟ ͭa]C/ww~3ʗ>{,=p}V=zs΅X,F5k;Ҳ^~Q* :>z{CSS:::x__ ְegg?=`ooHd3 PTvNrvv6Ri׃*DÇ~8*O>:gܥT*tˀ 僊mll=''~fkk˟ ]]]{=X}R JqU"rQQٙO\DDD\4 lmm???޽111zx5X| 2 UUU@uu`չpCLDQ.666⿷"չ nww(*‚&m[=GP2дoiiA]]]ZYY{WWW8;;ɩ_ 7:ta߫P(PVVo;̕QJ׮]T*ŵkPZZbrH$Y4Eښ?qQ{vvv<*[4 ihh@KK Z[[Ѐf>#[\r\'6l<== ooo1/=V\9nOKK $$%%"!!!;v, gyy9PTT2 (++38Nh,r뗥%$ u<&i΂SW'P*hllj466KqY[[ﶵ6ʪMMMظq#>c3PZZׯ\'DG‚x;vvv|0_Wv02=_}}=aw9a?􄫫+.yWd2~_˗/CPG3'Mee7bb IDAT}<}rnZFUU!P^^ΟkjjKm1YYYҒ_׸>N<~ }1,]q`+n8779Gr7}g13\`'s;D@zz:^ ^cND 㬻b…D xWaDWTd(--EII ?Q]]\Wmll`jjy]˵}%h'qty[nrC=('U(:c...O...pww.GJJ \+W %%Rvvvxw"˗/ǖ-[ tL,P*~̉!n:xIGN.VBpڂ{g!ssUUW" OOO 6(زe @ٳgѣ==JAVVX&1bk@gZ!==ɁT*Enn.V!M'aÆ~wW: 4WXXȋ1b"""z=:@bl߾V[UU/]+6DW8wOk_I,prr΋>yUrhoo\.>LƯ휣sr/~AAA WG;v >,yqڵnL&?p%(Jd;v{\.Gvv6QPP"lmmWWW~O': ˣ777QN૪QUU2^8:tx닀=JX,H$R=0(J-L8Aeeed:N'{{{~; agg7肿J%x񡳠^QQ?|N577'}-'sнow}"莲(|M[tXrrrxq8??|P+7.蠿gutty|Xqq1355/!$$ hHZ[[?8< S:88'OuB3gĎ;$3QR??q 'qimm WWכMK߸,>Dv:&&&xaÆ ^^^`z*lb1{$DTyyyGqq1_ׯ|D"w%:;;C4 N,WTTնO.Hkذa~~~b܌4^@r 233V1l0`ܸq7n"##}֭[#₯Jo̿n|^f㝁%%%())Aii)>Ը(naﹸ?o6@;-ÇZhmmBPa9l0LR!??9GT*#  f$%%!%%)))VammͿ/܇7wk>~:rssK*B*DC"22FBDDF#F?VXM!0c ,ZϟGBBR)B!F'b8q" v϶\_dgg#//J@'3V[9 7P(:"+??nD"00 Dhh(z)QQQugpttZFNN/ ǣҀ&MPzByy9yTqVVV>|8uLimmr555n8 ˚311[pڎV,[l|(J) Q099nx]G{/}ԄR\($ LJ9ݻwC*W [SSsS_mX X B"B@@LLLJX̋/___ТRGT\~rvv3BBBAq /… HIIR_+b1ꫯ]YY T"\VV ].C?444=[VVA:z>oMII 222Դ}D5jqmO~'bǎ U[EEE[]v yyy())綫+ Ycnγ{s6's>>>|'"_hnnƥK .… 055Edd$ƍ˗/#!! C.Ç~xOՆʿXRPZZʗ.[\\O ;;;>j+@.oYfn]Y&155;_YL]9-5 ~G/AeeeexBΝ;T* B^^^@pp0xޟFee%nʞ+((Ԅ\>”h^|3fLNXr%>M.1,,/;mڴASR+ާb:I5jԀe444ڵk:LHRjvutt]9~-^|Eu) ,^HJJBss30~x?əeSsn7~-Cc{s1\FzmdtttDʉWܼҞ[ZpWaa!&&&N3KPUUg"!!iiiJ|(sss#44>(NަNs/99iiihnn>v\r͛o|:_|Sܺ}ãK?xAwkL&Caa!ޡ{ȂiiiHMML(Jaȑ:I,H$h4x=j]ӕ :Ҕ^ޗj}jP^^0A@@Ӄq+x嗑 "mF& Lkk+~w/8y$ tznN0ᖽwRDrr2jXXX 66f¬YϢGRSS/(B\\Ν;8CRFDFF"00pP 6fpUAP(2d0qD>+vռ<<3$ y{AAAT*qeHLLDMM B!ϛAlҟJnimʂJFI&!661110deeҥK BP"88#GFpe"MMMիW;dKKK1c𗿿YwF={~7c"**e1HJJTT* :'NԩS1mڴ^Ϟh|Xzuʮs{1lݺtJMME~~>ӓ +1EVe}eggfff BxxN,FTW_b;/_ޭԄ˗/… x"._27DP+$$jddd=` @шgW"442,X;vT ý555tl(Jb`ȑ È#xq9pu^8...pnW"%%.]lmm1zhDGGc̘1tU8s ._ Z0L:MetB@JJ .^?)S`ڴi9s&sv駟ƞ={zDD"%‹ƒީT*ENN233@qDDbbb`ggghb…=pAfFŽ;0vXHRpVVj5y0,, l=Dh4"##iiiHJJŋQSSDDD}7hO?k֬g!::׍q\M.=99Ny.E c+Uŕĕ+WnYVbڵ˖r9<ݻw#!!C )S0w\̞={l j8}49CRݱ@$?'kFILLP(Dxx,=Xŋ=NRR4 FP:u 2GlDQ[QQ'Oѣ8qXL>&L`etqi@ `ܸq;w.O1␕ŗQ.'v-3㑚 Z ___L>ӧOǔ)Sxmmm8}4=6aL6Lx~Eaܹ2g|PX ZeƱ\]]1b,Xs΅K_d2R>j___,$k͛7wۇ;,-- 5Jgoʪ40B&hG\Y_lEСC=U&2SNeu#+ ΝEa̙8iX~w\piiiPTǘ1c2K73V?~'NKPPPC @$u $0g>|/M6(8pk;wVVVx#`n?ٳJDFMP(D"3 lmmQ[[o\  {}?3>*`…x'oh:ۃ  acc!C@VJC ADD&Oɓ'cر,Ӊar$&&"!!|2cʔ)9s& mj2c >}gDS5K(B$ARA,111X`f͚Xl6-<._ "BXX?a7n܀8#%ygbb{{{> 2 fffܹsx+DJJJcHNN /^UV!(([ϟǙ3go!55?~ Ѐcɒ%5k mÁ7 ??SN?(r UUU(//GEEc6h4={{Ѐ'zzXF'O⧟~©SPPP+++ 6 CyyN6'&Ć  h}Cvv6|||b֬Y8q͜f;j.]?#==xG裏"""&eee|RqaLf;wN: `̙7of͚5(xa׮]ػw/1c /pwwr~}d2|QVVHz(..L&CYY<<<|rC|( ߿}.\ggg,^K,qmUaľ}o> 00+VO?HTWWc޽ػw/1d̘1sŜ9sXe&QQXX#G8ww.] &8plق+WO=x V^p|)ڵ  q_6׹v8"%%&&&Ŵ0C` jIII_q \|&L hѢW>{쁟 +T*&Ne B233q!:tW\-ϟgyfTicRHKKz :|Iڻw/cHcR(!!}]&@@#Ё&3999#P(:p)JCC]Fk֬!sss;w{ 11,YBiƍmh .իʊ,--iՔch2zɉ$ -Y~W0iFhBAx$ Y[[O?MGVC`􈺺:ڷo=CdbbBl2?Hڼ>c&XL>(;w_&I$ђ%K(55f3ѣ 9;;s=GGfC` rO?8YYYH$SΝ;I.ڼ^#//^yrss#@@SNT]]mh h֭I( {=0iw3zٿkbqYY;G͍y:}4Ttiԩ$̌|A:~8jCwGʕ+I$Qpp0y2ȹ~:=sdbbB~~~믿ڤr7n(of18uV>|8 3gڬS\\LO>$I$rrr_d2b h֭GfϞM)))6뎩W_}I(oC`ro J>>>u~%&uttɉiTPP`hڷo{=TjhRIX4tPZ|9:u2=?ҢEܜ$ ͝;NLLwwB7Cŋ -[l IDATjkk mMӬY͚5Ο?ohZM?3=B!-[JJJ mV̌N_5 `ZMh tR***2Y^x277''''zwz3BFFY,,,ޞ~m1YܹsN&&&׿*++ mh}a.V]]M6m"wwwŴpB:z() C`%MMMw^:u*`Mԉ'h„ ONGa!~Kkk+޽֚?>%''ڬnauƭG݉---uV%t}޽{CrrrhڵdooO.\H/_6Y7JK.%tUjOF...t)CCDDϒH$(:s挡Mb0Hw}G>>>dnnNoQUrh4駟9::G} @ЁϏW_5{yH$E۷oge\No& :,--W_5,&z'I ̙3](2ݝ?nhnB&ʕ+̌f1tZ|9F WY9s&$h4|`@ yU<3ߙXP(> '''^zZkў={7MtQC'񽀿 ڒHÇ''''ڻw/0:ِ!Chرt5CD4gDajhh0I M(J裏ҒF;wСCӓw1hijj͛7-y{{y"++ɉ:dhs*zH(қoiDi&$OOOڱct0 .CΎ\]]/4Nt#ilڵ|/$C;vFIC -[hԨQdeeE}8u7P)'1drssi̘1daaAv2-9994~xŴn:*a0rZd  diiIǏׯ̎DG|۱cҬY`v,bںu+; 1 fy>޽$H6(NӶc R 6@ ~ 6>|iєj~8'N7yzz޽{I$?2L_Gh30/^$7777n\Wnikkg}ГO>Ieee}:>4ɴ47ڛ-{>ߎ,ӟDb6mdg}FϟO}>>a dffRtt4Љ'tl_'-[+EI#V;riccic(}b_OiO 7nz]ozArZx1D"l\BAGP&covvПLI$٦_&333:rH70}غu+ l#GH$׳_̛ohNOB44;w$P}D"Z|9/Q/ѣE.ںOΝ;G֯_gce^yk JՕfϞg%jhҤIdggG1:^uTun${EЖ-[H"ҥK4Pi۶m| %;=8xB{{;-]LLLرc}2&9}uW-ׯ79 b`ESyVzhn}?"=MV=ڠomF"2e}] T'wedK.%8{!@@8c 1o>'OVVV|r51Gٺ/> oxgΜ!X{̣^`0P(-}rww|9{Wrgk܅ ԴOhAW^x}PkK?=ĉdiiIK.u觟~"@Ч'ci,֮]ױ28xƈ@ h,Yz?<+ yӦMdiiI JKqO~Sec%Z#4#:"}H\ܕ>iƍʗ9OБG^ 9:?#یq47齿ONOV~.AnĄ+}o#<M^"rp;%W(~D6~DUy3n7'kPǝ_n9zp6n$g,mf{Ooct =>gd󡵵w.]2Kln￳Kr}O߇{Ю]6|dZ-7VTFZXwhJ(.ABΜgLU>Z,OqpM5}=EGj5. ''EKphagaJo  UT bQMqM7n6n5ffcʦ$h^ATJ2: y? H0s? 9{ձl2z衇4V~w`ӟB+KW(A( SQE)?88nݪՁD1oDDJ* Z?68:Ҭ7Sc9DJ" @'fRW_@h?NS*=Iz2Yo*R) (-DZid=ͱk )Ӝ-5E<i+?nG7jiʚxzi.r~ȪU(&&Fc+ 0aC]Ԩ1hHoRjs!}!(vZOU^f_Mߒ7Q}n:Xqqq$\cu%6NҬY4V͛i+00ms|Oy&ÇUVLOz䭧ғϒE{,--FW(A(´ۭWɞ~k~  u]ׯ(֣q\R{RSSGƺк6\A_,vmB;:;k:-K}}B~ $-(8*iWRC%Jo X$|~~o/C5 %9(E@^Gb ⻫{>_M]3 j*66^^^;hσ44X-Ҿֻ꿥#fVF>)J211Z#eD"ڵkװ= H79PBQHT$4>*Ro'+,V|EelսGBt9[E *g @SߠМy_c/Y6mڤՂfhY.}F mjkl>ztCy~Ȏ;UcK$@c@kQ]JAƦ?w!=U~XTOWԤFJUS;w]c8eultP_5kݜH2޵1#fӾ!ib8~Ӯz(rSnƚ5khݺu)PBBF0L+ .c5}]~dnnY|]~%_uuu;; ajT  9HeSTř8|D'&y[S c ر 2$}3bK=~810joCG}{j'Eo;Z?^vDNhna@lۣVc777Tcxh\C_O1PK3ZZ" MMM^v[[:;;aaa1e|?w$*(&Y"wD=Թz(ְVu_Z`AvJ~@]6~]ZYYidQoq{deeiܰ yqz*ըSf45ӽ W=J%}x/њ=h@ϯ#:wh_z+-//GqGkdffSc١BcxzV va666JJJ3Ngjm(THP$w%̅C􆞣z6ѥbbb?xnnLa"mOַ~ h"jc #/Ej-رm$pEApOU==(//PdRF==H$@$ puuŭ[4R3+1!hz8|Xv4(jk:z}ɁF}Fև=_Яѯ۷oC,kdn9Tt+~{ 1g?=xL42,Y'.z@g *5@(Pe]?|ygx=.d"~{4E8ڇ8q'{ߥ~rNvtO;-Xe0xtw~-ԥk!11k֬X3gD||WqmԩS3gʏ?UjqF޶E8BvpG}$+CϏD^^򭭭1c )x`Z^AA>'zdDttgn9~rI}{KoؿƍJN9}lԆ?-gcמʭa#HX@_^zᅣkHWz3QD|Iľ6Rapŵ 0 ) `m%%j@+x\~c& tPU;tn zj֧FEԙSG.̮e ^GVPp3cױ2eעkCLimmœO>sb…g͚5Gnn긇}  ŅW6螾TuhZSݻw#-- = />c+Z?T^6N#GGX7oRī:x@`9Ek8>{@=C\C%Ey|T=T*+VX=[nEll,2335V0P_c!U99k+)~聄ֳ{)8?*^yR)yzzի5^R(jLG}dJ3~GE "RӹW#+!HhJ[J%Z;(ԴRݾZnSL+66B!%&&j.K##]+ߧ3zP*l2Nֵk.sN֣.]*;yb=̒\sӃϿGSb(KwxȈ\zr9͞=I&i`ZX}/ukzwh 겨iӦM׵~ٽ& =d``@_~,6\yCyy9͝;ҥK#RI Pll7NBFJYYYdnnN=8~8c䜵PXXyxxP]]kkk`ZקQ=pBTQQ1"uVVV'M6*++GNt48yStrrrdrss:eטbO#R[oEv1"(L lBgϞ)22GNô"&MDԤX|1ܨ_qj /z > jjj49?Ac>6`5C_<:?QUUD̞={D]hylٲ/^:cB 6w 9|||~ݻ6m\ n#JtRdgg#..F[["##QVV:ᦹ+Wĕ+Wp8::H8wCEe0tÇ#,, &MB\\ yЉH$¢EP[[;"u2=sN|_|_|> vFaa!"""ࡇzqIddd`޼yH$#R/bD8y$LMM5^'3u&;/l~q \r'Oƻヒv:461\}>T BF)ANNVXuaҥHMMcѢEXt)=:u<477ɓ'q!xxxH/ѣGO?!,, #R灣=#Wb֬Y3gFw`pȑ{Xaddd`ܹHNNƉ'4}PwCss00 Qiz<ӸǏ|귵ӧ؈ٳgƍ#Vt9Q(x3/ m6ǩS''1c~XnȴwԩSЀ˗/#&&fDm ˗ގ@ܹD?*LA bܹABBˆ *X|1< (4妦&z7Ȉ?<\I5ɱg=gZ}.ˎ2-4AAAtyI< G}U[=t29s&҅ bCff&M>,,,ӊ σA!9C>=w2ޡo.Uh8kJ>C200 Piilill-[@ -[e˘n#J>3266H*){Ɖ># cǎi՞t4XFs4۷I,7|U[)::F뺘.մpB222/BK/DKqqqZg0m0tRI#2557xZ[[jSKK KGt-3p4c 266wyryX|1_Žqmڼy3 Z0\N#%Bmx׿@ {l˒1":tLJjKkk+=s$hʕU{ٳ}yvll,YXXO6D '}}}z DBk֬!l2 b0BЬYHOO{9J6^x~zH$61FQ*Kb<==)55U&dggӊ+͟?8Sm)1hmm/MFBz)жY]|2 /k$C#dggƍI__ϟOyyy6X|1p_x^z%rpp ===_~9  H護"WWWMש$*&gggrttkRYYIׯ'qF׶I<'OiӦ>m޼Y37 ;3~@ ,ׯkۤ{}6=C$hܹm ʢիWWjۤ^9~8͛7Pxx8/:3 |}}I(իu*A/BdffF۷ovmC\vI(Җ-[N&ș3gh$ ӓvءS8CH$?А6nHiii6Wb266{M ciÆ ]&cZߋ0w^"@@...m6:wK3HYY}駴pB#z)77WۦE}}==S$hҥ:a<---c%www:zMBA| mݺmcqeڰaӴiF24|@/8t1>/_~)009m&!!VXAhǎl6cq-zWŅD"mڴiTJG&&&M;wbhtzHOO̙ӃTͥm۶ң>Jf(nd2D$魷ҹ}J1!=#tc::::l2 @{XD,a=Yʭ[W_%@SOё#GXc #PHvZڷoߨt9sf͚E֬YC6%_9;;)بm>C杳cǎCctttЮ]($$Ќ3tp1&@@'O?xTh1zݻwShh(`Fv8g!+++ק˗Y?1jihh?M0?QqqM0EEEyf244$XLk"11V\Ii׮]!JO>pdmmMO>$8qM(a[ZZZ~kג1D"Z|9޽{t>s:qDFeee6蓜zёB!-Z80,&Uɡ۷79Ї~H7nN/im6~Ѕ 7RKKMJ%>^f tzĄ,,,gDư2VJ=-Yښ}Qڷoμӕ荊 /'ccc222 6бcD;PZZJ/=쳔m:T*?̙ChΜ9?~qqq1رfΜO(yGᆪJm`hOiժUdffFzzz`Fh۶mdccCpB^rrrwޡPFoj۴A ׳DD0%%%8|0N>sΡ?>'j ChiiABBΜ93g 55DiӦ!** K,Add$mFP*G!!!غu+6nsssmf'`޽011?6mȤ/޽{Q^^3fC= &h<(";;=vލ9&J}}=޽pttă> 6`612e 6l؀'xbiGRٳŋH\+VMd0CСCHJJ!,Xk׮Mv_/'x<mC.\ ?# V^gy6Oc8~8ى ,^/ܹs!m&1hq9?~Ǐǭ[`ffV^ '''mq?~c҂^WƤIm"c@DHMMO?"##> B(jaHXR,\xN‰' Jaee3g",, 3f9s`gg71 HIIAJJ ^vxyy!::шMq駟/B@tt4֭[5kT1IQQCA* aaaŋY1"p}/(**--[,Yd\RRR"66RgFLL ֬Y͏ .]H$ƍOWKK q) D:u*f͟?ܝ袢ɸx"VX̛76Uk!!!ƾ}PQQXpDGGcٲepqqѶ1DYYsD",Y+V`ɒ%c~`MF(>tvv")) W\ᷢ"L<!!! ŴiKKKm`RDAAҐ+Wիhll1DDDY& طo{\pXj֭[D&2!77?3ك899aݺux1{lm7bĉC\\aooŋY[n8z(N<&aٲeX|9"""pu/CRRB!°l2̟?ӧOg.!Dl;wGř3g܌@,]˗/Gxx֎*툏ٳgqy$%%'OFdd$"""U!P(+W8{,JKKajjs"22QQQ liiÇO?ѣd1cVZKb%jkkq:tGA}}=OUVa͚5ж:CAAΟ?\t ___̝;sEpp0| ==W^Ebb".^BΝQ$ /^ӧqi$%%A. ,@dd$BCC!m*cQPP˗/ٳ8}4n߾ cFpp ,>B|]➨IIIHJJT*#  D@@|}}y&n޼tܸqhiiP(Ĕ)SA da߾}سgd}݇Kbҥ___m2 .\3gpYA  ,, VªUm3GHLLrW^Ekk+ ӧc ԩSaaamc\~׮]qu@PsܹsPiQISS<}4nܸR WWW>~3f'+~VWWC$!$$ ,@TTfϞ=gt (,,Dzz:n޼7n ==999H$”)S0ydL4&O<.>|2̘1fAQ!??)))HMMEjj*kkk>3g !"dffxC$!883f@PPQB[xphnn=0ehkkCZZ-''JbӦMC@@̧_###HOOGFF]D"vdϴ9H|… J|2u<~rB?`DDDWi r|KA 2e L___L26666TTT ++ 999Fvv6222 Hnp{HeѤ$TVVB__:u*lƱ Ĭ7og`kQ:_=@vv6ӑ'rss077ĉ7 777i[0t ]%%%P*pqqI0qDR3#'N… HNNFKK wcs>D rssq5\r HKK\.70|,Y HIIŋT偈'N L2_Z999BZZRSSq5HRח@DDҒp4477 BPP|}}˞S:@]]hhnnPAAA Exx8Y7d2 997n@FFP>ド'ۛR D|#++O ,,,9s&O P(pU={.]BRR***`֬Y ?UaJ%NKKD6mBCC1o<̟?6yPTTׯ#--O@{{{>qq b„ 0acTVV %%%(..FQQmmm@Xe4i&q%TUU<==yg?((~~~pwwg"T*ENN7n7."Jq5~]jj*rss!=񁏏<==!Yh(--E~~>rrrɏ:FߟOsI}VhB.IKKCCC; 3Oɮ0сb ++eff)x!00ZB@AAn޼Lgnnq vIDAT.fff7@+\]]L&CII p^^QPP_GSSS f)..2ڵkhjjxxxްԲգ Bb͛y---BHH:lQ.\VVgii OOO>yOOOB,ёt\J%\]]07 &}"--YYYd##.1\\\\X_cimmEQQ߾֭[۷ N?0777_0c7Y555]\bpvvb1GGGƆb娫C]]jkkQUU2TUU|r ݽ sw<JKK,Qu5ܾ}DCCC~zn ɓ'NNNcz_SS:ͷnBvv6*++ N6wO6pF@nn.999hnnps~O{9::g]Z[[Q^^.AD";/֘2e Q羾`([fi?ɩfNNN7-d2JKKQ^^E3ʠT*ܙӎXv}Fj L0nnn\X,سtttt555H$(++D"Aqq1?@w%o,X> "vIjfdd ;;v'N\]]4.zMp? sOXgֆ.>@$~eSfd2竬keee(//}Ъ* pvvyWWW1Nee=򐗗BNNNpuu \]]^j؈ TUUDOΒH$᏷ĉL>t_1_. YYY;)٨|⸷077%̺>҂&455hnnL&CCCd2 VjkkQWW{ʴ>7;ɉςndCiY$ٙH9pttvvv:^6L*TWWRH$.3u0a„.&O OOqK$ PMܾ}P(vvvpttX,GO0oammaG ---G]]555R~0j[b``77.[hԄ.zQM~#o [[{t3:]4}:eee@EE˻cllܣn8ORZZ}nmmeoo{{{ﲷ,--aii XZZ VVV~Ё\.T*T*E}}=P]]*TUU>^eee%.%Ϝ_˖+tvv .zm~&9ggg8;; a666:={~%%%sѱU&Mbn\ee%$ D"w|![[.N?9,,,`ee333"ddhjjL&IR^][SSepg87ٙM`Eee%Sq͊ ~pgKR5M}( 444(VWWϏTWWw)Tc\?m4#FWX|}Yxpm2;055!BfffD0221-w Iޒ8fRK) ڊ6tvvKaw9Ksn;+++wI[[[-c4b @QQԬ羵=`mm ‚22cԴ=%iQTB*d!Jy=q Wg b1\\\̚7www8]ӊ AP{0119D"}k= UT Tv@& hmm\=6nbx9q@%K먪 uuu=6n`ll ###XXX@$ҒRw}\۠>"BCC&tvv=N;%ߑ:K*و%=kjj$CUQ= 4~XV TqNO:N_`[cc#r9V555hnnT*Ecc#2HwLLL`ee;;;888dl1|(JOzW^^~@WƆps'{.vkl%Tc\ݶpqqX,gRsIqn0%,l:$ggMM 1wD033,,, ;R5>SUqmUOT}K.r an .A%IёfhN~Pl(r .TQM D"X[[C(򿛙#u=nw(kZR)'X~wX'7I'ѹ},`QKk .utϲUx|Ay.]=9'91r jucѓ055jCQ*|u8DZ |O& ΉTu܀{ڽq:\GԔ !SS.#vvvC4\|N5q%s{kTi_OTK2q3:l cഢqihh؈466}6Au=i> .э~XgkpZQu}涶6>p S*&zUIsT\@;{`;Җge2B2Kϖknn\D0GO5vӐ)?Pg.y~gI`.%hT7Ymmm|~-y{9%V6Yq\-UgPssU+p nW(J~9]Fz=n-:KDs9ʊ\bddei&`u% `0 `0 `0 cæ 0 `0 `0 `0 8% `0 `0 `0 c’ `0 `0 `0 1_F0 `0 `0 `0 cD){m[ IENDB`doit-0.36.0/doc/_static/requests.png000066400000000000000000011206331423054503100173160ustar00rootroot00000000000000PNG  IHDR%]bKGD IDATxwXTw6{ ԑ.PP!"X0{hK)o%uKfݘla\ 2(EQiHve;p(&SFT"ii~w1HȥRXk Z&L yJƕ1$"""""""Oc0΀k:t tIUFHT"L [-vr)-dB&a%CI""""""""u4\ -[ǧJ$-кupmsL  ,r)Ya%cXIDDD %@ހZ5Z= ROmu;2FF.BV2 2cIDD0$""""""""5 PѣIF}U%IGT28ZɡcRƔDDDCI""""""""RF5=~$Jf#Awj$ ZOOepn (少KE:CI""""""""tf=TM:77!S<;E F#eRZZ IIDDԣ1$"""""""NQ֨A@t-%HrYbe$""iJQPڨE5- p+urpg@IDDc0$"""""""1Rz $hJY$1 nW""$ݗf= 5P5j!1Qf!bp?K[FI""$ݓ-rkѠ3@*@%&$tp]]P&rP3VZמdol`I"""1$"""""""۪pf=@$!$ט` $\tH, %]FuV7CpL+Xr)]QPnҬlZшMfԃ~>4ۙq:pfL3y WG Z Ү^c/Qc(IDDDDDDDD& :GUנ~ˮ@-]a+l-!q=(#uHhw8QV""""""""F)GAOcA|LabWqzqI VW؈] QNI""""""""iF@>M11I#v)F\mFͨ%""b@DDDDDDDD(ӈ;R[cT:=oK7qu8yWs/DD2s8GHg[誯S5+|}*72cb>O^ Д1WOA@Hn/j:$.6aؕz$""""""""7 vQQԭ8_+> +H߿Ÿ_<j0VŧsgbkBļWⷿdzϹ!Oq5mB/}c0<50ױGdh\1vR ?\n2g }P\A:MH¥4 zG qC8^h6LDD)IDDDDDDDD[ EAb1H9 >^p@w |k *;I#U᷐tQ~ _/ gpm;gz dR'd<4? _;ϑYo8dycҳ%H'/C$a>hu R""Ŀiz[[H$ae*)5_ύzcXv)t ^wy[WO?h6wV0j)IDD5ODDDDDDDDt+2)$u#4MM0梨_WFF47>Htr^&|`ghяa[z=ZӤDDD^Tdp6gy]ѠJw!PV`ST@`^n?,u?῟7 1_ E8sz= (ۻz hL""n$6ݡ[LS*(Ì0cX?-~c&y`:{ =a &!C;~BQIlޏ\RC>63kF5݂:# O!(~Pn{?Baw^'}z򦐶67'iz qBw&""MdbADDDDDDDDH 5^=3lP6.lFO c'f}0@0 .gDGQP9㖅rnaŸo g .G8\ugƣ.ӧb n9Rp)G.%{b[ttuUE28x_Hn j2É\0):.66lj|4 h/Cq\>$C1xkWchA#aof?\|'Kl~\VR)ƹAmG """"""".Qو z#. ?b.\0K D`=m,.O]uZ\z2WTTjE.w\(>إ-!G$c_'S ';DDD]$QЀ*Sxc^әh# O''x8tDHA˒mK,jRg;`<:KDDԕJu#jPզ[YYMnWSSs{( CP@PAAA4h  Bhz M0z8W#nǁM_ K@r߯ ۖkUrƺءewgu\S4552PluJOX[[_sssT*ch Hh@VD5#֐JL$]hllDee%QQQ ǣߨpqqMcS۾z/-]5I}D+9F@+#""&&"""""">5dTT4z` ggg899aȐ!0BjYYYƹ,(3!??"G]M$o1yxLx;]QNI"""""""Q]]}=ouLFݽ[t1vJ,ϝ;gT(ȑ#1VADvOZ;#Z˱e՟p.N>zXZZ"00ƍCpp0ƍ#G z*DDDDDDD-1dm;MAR aÆ! #G42䦯@yuT6 e8I]L"`@*,1,df۔BT"55)))@CC,,,`m񰴴`z9DDDDDDD%2vdzgϢ@KСCo z_נ3% Z4$gXZZF@a%%<,!ݟw\(Jhll4 Cpp0FLv7&""b(IDDDDDDD(++3غc{o]մىt[uu5Μ9L;wΝCNNjkk...9r$1j(1FsT3Ak:4 ^ɓPt/^L. ؽs9 ? DZ9_fAezz:`oo SP9i$ 0`(IDDDDDDD&MMM7u-JB d\Iff&233q={'''! t9r$EVkFʛui$ ;h]#R.7^B!C Q\\ .@cܸq[HH:8~8;B.#00aaa8q"AGMDD{1$""""""*++qUSW755;p@nnn|NƎUSSsATƯF@@iڕtzT4PިCAEI} RW[9\l,0J`@FFvލD&LѣP(RF '' iii)ҠhLMɵ)nPRwd,++Cqq1Zپ]7v/ӓ';YkckBhj [} ȕw&jjMz\`80^}I%PXh-JCHLLāPWWC"22ӦM233MXঠ2<< ⾏馐رc~aSHCDD0$""""""4 jtjEEzi_+++ 8!;\\\ E<ҾKVYYY5r9̂Ǟؑ5Z=j5-nJF&X`/hhj'b\ ,exAzǏGbb"vލlaʔ)={6"##accR(J:u d>|YP Bjj*=TC&aԨQGxx8&O 778j""$Q'2* A*xԖ^GNN222Lk?fff@!00ȕwO::5:IMw[7ⳤeIyK|-ZH$g)C+9[R :{)ټ<ٳ{ő#G0n8̟?NJRǏt@ې2$$VVVUOYY)(\\\:@DD0$""""""MMMw2l_kk  1=NÅ ֵ̂-,,k8n8]vRXX 6`Æ |2&Ng}hak:tk ^Z$zE"zdpB; )-d#XBCCgܹW^E@@ϟ `رfTBV<]7.ihhcǐT9rZCEDD"""0eʔ^a(IDDDDDD}^kШRP^^\z(//GYYPQQNgO*puums}ƞ8}4qidggCAAA;v,ƌ1c ݏI`׮]ѯ_?<xx}MzzMz :A0^"$׻zI2#p7Q׿m `%N.rlRȥKa#vzcGGEbb";ٳ1y[ cdQ=~Aq>|ӧ!ӧG5kQPzWEJJǺ:ZDwww  )dtvv^iii1==/^ pttĘ1cLرcC{dgg#..PUUS"&&밀W0Ѥ7B#7# -zu558f}yS7/vjib 𞷽 & kyg#펵K$Ia%V.L+ֲJ&,#>>pttĬY3fB^ϛiiih4ׯFmT9ꪭő#Gp!nQΩHII1Q(FXX1~xum8x <$ӦM3^^^% ""t %[hVmGJ+vnnn* ȑ#MŅM{16m͛a00{l`ڴix { 6LzhČ3PTT XYY]RUXX|w8z(0c ,Xsνc@٪ rrr`4nMzkH#)) IIIؿ?MQFFF?uK %Ӵv5ުZִ%o<^#L̙3HKK3Z׀8p ƍgvcQ+))_kҥKFtt41p@2sƌpss޽{"vIw=z4^{5b+\z;v@BB:L9s`ҥ9s=w!33,p(۾_ss3RRRo>ݻppp@DDq<'""H %螵^X^^AL:SiiYdzz:@uZ-8M6!!! / 3s ?ǨQk.Oի!^MM vڅx۷={6C.j(J@TԩS(//L&~vUPP` (466"00PwDDD$h *++oըVQTTz} E݌mGooyGt'UUU8y$N<'Nɓ~a?tc-lܸPUUS"&&s&&&bѢE>}:?ݙ  HMML&^۷oG||Ơ255j5YyyyصkvލrL<sٳ9效:CI"""""f7v7ִL&3nnnpssàAL׬E<:ˊpQ;v ǎCFFZ- Ə & wJؼy3 fϞL68ZK IDATۏ]f ~_ᥗ^¿n_zwٳ:tYYYشi6nJ,[ ˖-{~`@nnYP:AAA[]]]d޽;wD]]0{lDFF",,Y "$Q7:FV#To^F4=zNӧOرc8z(=br"44B$y7RZZM6aڵth 8PʪU[o;^uPcǎ+v9}`@RRn݊444`ԩXt).\i[;iiih4ׯFmT9HNN6uQaȐ!X`x L0?{C0$"""""Bj6zyyB##;8uRRRGM#X'M].@شi(ۇ1cƈ]R3<]@bϞ=X~={{{DGG#&&F6Z R(J:u dptt^Guu5tR,[ LDDCI"""""uQ]]nZQQt:ikkkpLJ i* N2'ONСCf9rإ=*--ŦMW_ŋFtt4~^ZZZ3g=.EDDiiiٍT*aڵx"&N+V ** 666bSnTjH$Fc„ xgpBN ""DDDDDԣT*ݱPզ-,,///TOOO^􄭭GE=??~' ??rcƌ1uAER>hZ8p6mBBBh"\Wubƌǁ)vIC`` }׿ ]qqqŋ / ((HnT߿@AAAD"3yL>aaaW">$umǩpl}|\vʹ=:V~~i=ʕ+5aaa4i  vrssa:vSNEttt(iii5k ={ID;?Dvv6.Ryy96l؀kҥK Ê+hѢnۅj88q: 6zxh""; %zT*\r%%%())J2 xxx <==//^w򜨫]p?#>Çvvv ɓ裏"$${:ر6mBRRtR\N?`޼y ABB.It DPP. HNNFll,v܉`Xrew^aϞ=a Jqyh4G6 *9 a(IDDDDDDբ7^rz޴BhE<*ީm'PXXh I&uwlN9s &&ӦM]I;v%K0|lܸz=f̘}a̙bCIRaƍOQRRYfW^j5_"==>}: #%%  Cpp0ƏWWWCI"""""%F[S-++Caa! iBnˋ'ŋM]FII 0qDL<'Of'd/TVV8[yyye_b6l؀˗cŊOv];͛l={!pZ;wDll,^x=\+++ 6mi̙3q(J-''Ffݔ}~,3QOPj(--EQQ QTTb\r())AEEi{KKK4Bc777H$ >|III8x G EBBh"\cƌ.j*[x]r+W.^{5ˡO?[n%y2dإݖVݻa߿Ɗ+L#\ꐙiTfggMAexx8 DDDP2PT(**27* ,,,LMk9zzzͭ[#ꋮ]#G 99III8{,r9&LiӦ!""&L`'d/ 6`Æ 0uEEEuwF#^u^WƯkK?aɁPºuL],X0aإQYY6mڄkŋ4iVX'x jj5J%RRRT*q)C&afAepppHD]1$""""ꁚ o\Ƒ>>>p$ 222$Oh4:t("""ӧcbJ شiၧz +VQ`0 &&_56n܈'|RzFѣGcܸqزeP'{7;v x嗱tRr˻-шdbǎâE/'SZZjMyqTVVB., ".Ph4())iw|\t 555]agg'у7DMM 0i$DDD`֬YLJزe t:̙h̚5^XhtR߿۷o?.vI=ʮ]0w\:tSLD)))Ow+Vʕ+ѿK#J7/DAA)\]d]M1LMMZ|}}M#_uh:CI"""".VoXVV˗/ƪZZZ,llxذa"e***pA߿JB)SL#Y.HYY[[gϞE@@-[{NNNb'̙3Νݻ&vI=Rdd$._ӧOss>7ny+pqq;×_~{˗/?ww~\2==MMMGPPYGe@@0 " %:Pcc#._+WW\+Wpe z xyyn򂧧'\]]E>""l'N~(Jd2L83f@DDƎg;"~ᦱ+Vرc.[PTxQ^^#00Pz/bXj^~eˡ.RUU/| ]{*.aڵZFTT^yz?,LKKFA~0zhv#dJ݃ SXXXh [WVVׯ1x`qРA~="W^ŏ?ݻw#11jnnn>}:fϞu!cذa***0uTDGG'u1c { 6Lzz _~%(v9ԅ4 mۆ{x'o" @VΝ;QQQ.DuVZ 5k?`„ bvהJ%>clݺΈK/ԡ] ʜFuS1DD7b(IDDDD}FAIII9ʕ+Ѫprre8|pۋ|DDԝc߾}8p:13g̙3#F2I$Jزe t:̙h̚5zoĉQFaΝ߿%*_}ydddp4e&v؁oHKKc=?VPPXnt:~iW|^]]233͂l) B:zDDDDkv._ Afݎ>>><)LD`0 ##4UTܹs1x`$T*l۶ ֭ٳge˖c7$&&bň7|@`Agˡn_xw&vYwڵkذa֬Y/bΜ9x7 iiiԩS(//L&͂`L#>$UUU|2 peӭhhh0moooZk;Ƶڈ#j8pwQ]] ___̞=?1iҤ^ӊz6~رclmmxbpd]ڼy3y,Y֭z̝Ø2e ۇ3g]u)))xw0/ɓ. ={7xH$]VGiiY7QYY \???2$$VVV]VQWa(IDDDDFMMMcT  <Ɛ!Cc@≨盺!9g={6fϞKn… زe ֯_bbٲexakk+vy=ƿo+x饗Kjř3gxQIII;#C!,, _裏]=IIIUg7 .]*mT;v /`C*R'QGa(IDDDD]fUji[Bac#??Xz5^|Eˡn()) .RSS1k, {r^[n^yĈ>F`0 77׬2==MMMGPPYGe@@/ $uF5 CǶk:>" eHLLDbb"8:5 ~3;K&Jغu+Z-̙h̚5'`0_W_}?˗/>_Ǻup8::]uS)))Ç#""~!F-vY$//?W_?/z8ٖ^͂ʴ4h4G6 *G)vDDPVEqqMacBǶac" ر !0ydS9x`Kn۷oǧ~L`ٲexg,vy=VEtt4v܉O<%IŲe]usIIIxא G;}>s8887Ċ+`mm-vitpRSSR\wwwrpuud"" % `:k:U([vlD`׮]ѣGaccS"** sE.Ap!bǎŋppB.O[f ^}Up0ݑ 7|?/qiTVV>5k`oo7xWm8ֵkאaQxSP '''K&>$QS[[{SZ`oo!C`С톏;V|G߿?{=T*{rU|7Xr%.!--R:u d>|YGeppp {c(IDDD5771;;MMM+++xxx:1WQjqA$$$`׮]ȑ#1o<̟?bHݔRDll,n F{ ˖-!.Ō3`ooS쒨 шbϞ=bC=JŸg]AAA裏0i$˺gEEExxdɒJiiY7QYY \???2$$GD$.DDDDM۱ۑ`c[ŋ/vMj>Ù3ge˖gq$iii5k{r}nØ2e 1uTˡo={ 22q\8""fǶmP^^n "9fnI:tرclmmxbDGG#<<\z~CHH vIt3gDuu5N8 $%%WrJ{ׯeݳ;GDD>]V2 5LOOGSSdQDtDDDDݎDDmBRȧz bHX^^6oތ 6SO=ű]dΝx'1|lܸk3f DEE]p:ׯ{Kd"Wv### .?O]V8YPFcԨQfAȑ#.DPT*\xۥKPSSСC1l0⡇C=CLJݎDDwAT"..۷oGii)\d .vyԍ577c݈Err2˗mذ˗/GLL ֬Yq=O?'O"++A2uJ;?> !!!buόF#oߎ7xw]:.\@jj*RRRT* AnR?bLD]$]h񘕕l#//8=S}) IDAT!  H$++Hy-EH׫RK(R  RH9)z?d}gC7kcųrߟRJ9_![TT/fΜ9;vLΈ$=w\iڴ)!!!j ɤw}4gڴi#fΜɗ/qD.wEy/^Lݙ0a^^^zz(Ge]Ν;3n8=vl;*CJJ ԨQ#ێJ???|8[r׮]X,<==ZjBMsVUCQƲ۔ 12%}LmxѠ`Td()B"66VQQQ9r$k2Ev...:!CUU֯_?ʕ+qqqM6t҅F$iFM%ŦXlU%͞q]ŦQ|&E(RрɀQh٨l0b/3gΤo߾@M&LHJJj߾=o&uy9Yf)R5kH81b&Mرcx{{Gqqq 23gҶm[LޱJrr2Ço^`̙)SFXjȑ#DDDݻAUU|}})ԩC"E{¾}[c9MհVUMm4v-}lj5>'Ew٠dPp2/31v2yRB!cyͻ>Lbb"^^^+W]+VOBΞ=ܹs1cǏ' ޽{ӱc\wNdM%jU%f'ɦlbOIQHѨ)Ko (+o 0\ nf#&n&#NN|J͞Ux4( hd5Qn̞Y̬ &#NEJ/gNF<11vڱnݺl#o',,^xၟC<}W]v4nܘ w$|?cǎQ@<&!!AӪU+OכV#FPfM~gslCd+T6lؐ~ߟ{ԩ;( cǎO>9mU5$J]%ٚ^Lي4M!Kr6CF%Swl0jJ/Pg,MFsK)J !8|0DGGzfa0(YdcfTR=Be̚5WD޽y0z{(qi6b-v,6Y+3']r}Q6VEX7.FLx9mٳgi޼9jf3:/A<ͣ{oߞɤw$]~eһwo <*,,=z`X駟h޼ޑy7ٻw/}NNNzٵkWVrΝ\xHJ ݻ3wܬcl UTaƌ>סIV;IV$kƊw6Fd222 bJ_l=I~'#FǝsB|c每?ifJ(f5rP!cKIIaL>;vP|yzA׮])V$ٹjZ)VlA!c Bg>& 8r6e޾};/2,Hf2\xQΪ&O߿?|Ml"3f GɓxyyGQ7ի_^]*Ug;seMm6\dbŊيkٙ+rlh4bԩ&LxlgYZUiv[ovBcoUx9ѐ2kgR9r6Ϝ~t6bt%B<ܹs:t([\xOOOʗ/G???*W#ڈ!駟HLL_W^4j VURV.'g!fPTMä(ٵ6=cZ EQ0l6LoR5vX<<.!!RJ'Gq/歷ޢH"̝;5k=z={m6F| r8M8z(;wd׮]ڵ{իWgwfׯuk;i b-v,VȢ\%IA&xs|%B\$--3gd3INN;xcL27,PU0fΜɲe(R]t/^\x$ծr>4b-fHr)iƒ; 4Md ggg\\\pwwOOO ԫWݻJčv;~`ڴiKH"O>}:'NC8";u]v%"">aÆ9B[MӘ4iC A̞=bynСCڵ+W믿 *U̙3iذmjWj#bjD 5s,cnٚ^V0ft2)^,b}7%B?sKÇc1L,Y]իW'zBC1k,[N8Aƍݻ7ZrsT .X9ƅ4P4TO/0*J|AAW3%ܝu7>1"_ZZ]t!44sIW^tҌ5;W_} d֬Y(QBXtؑSN1m4:tw$&NȐ!C-] .̵TWRm\Lrݚ~% B!T4* vMC9)j.)J !9ӧ!::hbbb8t.]ݝʕ+SreTUT|899^!ؽ{73gdΜ9899o0p@@*R9MM ޜ)kcRJsL>Ғ.'KJJM6DDDl2^|E#weɒ%?r-"rЩS'Ν;ٳiѢޑZJJ ~!&MK.L:Uv&;;h"v=7:1l6W<"%cGIQ(fL73PBr522˗/-WoOҥL !b/߲o>۷/:tMx$bT.&[QU@dN}qw|~ggWn^K/đ#GXz5u;ȁΜ9Cr2e ={;YRRRxwǀ0aCuիcܹ4h@H>.]'OG< pRX%JST4@O5@!g3usd RB!k׮qA:ā^ @BV~~~Y~~~,XPB!r/2m4ONll,m۶eYd؈M!6ՆAQP֡e.bU9Ĺsh޼9 _+I`z? &&ơ>"w={6}v/顝={]yfFŠAdrwֹNNN,Yʕ+S\9ĥh )E0Ɏs%Ig;X[JQR!x8z-?ixzzR|[v>-[VB!rHNբk׮|yRMPl ҤEk.(NswWL%&&f͚)^ޑDw1*Uٳ;#ݻvڑyhҤޑiL4!Cܹs)Pޱ\puQ\9ʖ-ob\Nr⺅KVi*r %=) ?%BѣGAUU(_|V1 ʔ)<哤B9Ʒ~իPo6zr^:TdgdndB/dt޽`J.oFB$DǎsKH ٓe˖'0|p^ݳg[F4.]JZ$B4N^Oh|** C\4XDEoW <|7)J !jriؽ{wVvf3*TȶϏ*U8ōBǔ8q"qƼ;(&٦r;L1 Fj&&M6ѲeKjժɗ/ޑ9tժUc+z4M믿fС4oޜ9swv:t@xx8SLGzG7Q58hHl*i&Y2(b+8)EI!6SNvX,L&%KVx r@!xOo6HJH'J2!WyO<[_7qrPڷoOpp0 zGW_ڵkE]vxzzb ʗ/wf7nzI%&qZ v )rMT/@m]()":<'22H&-- HٲeV~~~TZ*U,aB!rW_`)Bݻ7zG{d'[8p5Yo>R%=P$̚5={һwo;!͛iԨ۶mnݺzΝ;k?âEhܸޑIhh(]vjժ,^___#Yvȫ\JE'" Oe/w~HQR!DgZVҥK/^իSZc*Upqq9Bqw;իWS|yG>}rOPl1D_syN1vX3fqD.Pn]ʖ-/w!)55^z`&L;CwGraZn˗Yp!/ޑIi쿚C Lcكqw^^åjNFj~]RBql>ٳ[} N:)RDB!}SUիW_m62dCy;츔w qYҾ3xvjuxPU:X<*M2dǏg{zGļy֭%K;iƍ㣏>gϞL<١;%ӵkW~7NJϞ=gDǦw|1`?'M_U@ 4>FY~ BvN}0k,~Guw$k׎?ɓ'3n8qO0dʗ/O׮]fҥxzz믿2rHzs"(Z ǯKAQ䌱i+Cγo&4 .&R.I))"KHH`Y;w޽{INNd2Qdɬd@@-[VB!cq5Nw}GBB]vߧB zG{b]HŊ\ޛvuCRyn(bnQ8$BǎYv-K,!88XH"=z4cƌϟ_8Bܷ{ҲeK\\\XzÏfϞM^xW={6zGʕ,Gc۴ލZ2DvJBQBgvŃSR!Drܹl;F4<==|l۶-ԬY777c !ݥK:u*| ѭ[7 BbDZl\N!/ɼYT;TR8/秱?3W__@#\ꭷ/`֬Y 0@8Bܷg};vвeK6lիUޱZHHŋM64nܘ+VPP!c*6R 64PW,?B]\~#GdkIbbQжm۬3 ͈B\ɓ7ooo F>}N'aP@}3:i9u=;_-`Ο`#PxoB;N;fsc+b(^` l۰97n,a$yUNQtq cmҊqp:т,R_/^y\p?5jIbބ0qD~mFY@ GѢE ]v<,Yt2 IDAT͛N-_>IelH?Zy}lk]+j>[ǧGOt0]2z#?(ٱMSVq{<%fڃNpaM\H͸rx?^e\Kq9ŊMgKQR!wu"##w&&&UUɗ/+V/k>ޱBرc|̘1E2fzk]J=$*S-^1)6~5|-AJ; =|k&Q+m S;30P}frؒ ٖk|J^;fɻ>Om=,zc<<7&^\IZ:R\:M?B9#`m&'lM~=̠}2Q0}_*iJNR9~u/,3X?u\6Kk8bh4xo38qMb믿(_ޑD2c VZE˖-#qww'44>}вeK~':vw϶mxW_>[1P58i$@][Ҋoڿ͡Z3W6j.H!oRx?8xNieo.~s2ϔ|:=oŀ"Pỹ(I6~O٤4)J !xسgwκ4"EPfMZnM͚5QeʔݏB!W_1|J*ŤI޽;f9рT= e֡qǵRњP!X |& kӦlM"5EEmgϠ•|j|,uD gPTgL 'K)xCgq*4z/ j~\$>fs!) wz',)~8(5k7֭EQB8q%C2LйsgΞ=ˠA|}}ٴi۷I&̚5vˡ%ray}l~Ʈ-{q{ޞȥ?s~to#kȴz7QXO2kqZBe}8M؋HfPpI˧LŃRBؿmw@z{{Gpp0wd!"ػw/Gfɒ%TZN:v=sx2G).8gmJsaN+~w+S/m+?jzb%L~v}ԁ9C{ي}+.@mnoG)@Ϩ|0;7WfbL^Q-9sKv(qanw}v^z%VJhh(zGy̻K&Mعs'k;LQƌ/gΜoqE2`:vHrr2ݺu;ò=6#Ϗmcƿ= fC"7S V"י9mXZ,5ͫ56>Ce0οbi F )gz2ݦJ ! o%$$رcm۶,Xrڵ[/B?hܸ15kٳ\HBBBtA(r䞆%%N'OmWHSAM&5Q&< (w|t^!R~Ҕ`|k$gmݳ!}R>f eū :,E wzjhРk֬Eƍ^:SN;dL6=z`;n}ԩS裏ѣ|>cN/䥱ee%$NKbX=3TSۅ*C2ӆ5δap>Q_˜v?9E!n)(z{{R ܹs;w+W2rHڶm+H!65jDPPFDD/îO-)W ^x/bْ >c>vG\7~SvAT9 3DHRNLNy7ϧUViӆe˖3jE[opB]w!I.]Xb-sl}g}ѣ߿?&M_p5p5RKAr2e+9آX3e7%gְvAyO߇]ύ12݂x{2~؆n^NKߴZ ")]@-@?>[W^W Bh4i҄ v;aaalܸ^xAh9ZbRՌ5j_!_4+mи pkCz;|>l?^mO.1NG9y㌁ KPc_>yk9kgL/yZTŤrl|'hxR)oR0?SFO<+`?IӀ=ސAt+f̰W٢p^Q@=˙8}18ᣑ#3E[cslR7OWɓҥ }d|tdbܹzG5oޜkײj*:t!C0f'!LXfǶwm{0*!(nֈ]՗bY;@Qo,0Sitˆzc'td粒{=;6o&j^ND>fx/uP2-d9rȧF!^.\ɂ (Tk\ t$1a(R|[a7)N?pJjʼ2 FI+8wXG%_tC')bH:iz10$t Rm-َǿ`a\ndi#>ϾL Px/}~ԋ_q:Hc\Q@Uq.]-L oiCg)W ŅWF-0gNföc$IU=)\*>O~jΠ(;&C1Ǝ|;v9'N`ٲe})^RxOUV݊?00> 5;Cd\_ۦi:v7 ZE3\<ASJ]oy)RT xpja몵l]˚'>_b_KIlXLX26/_f),/?5{YSwӭi:!xbٷowκ:t___nu֥p:'B!rpFN`` F]p\ GRs@ջAK[O~M \wnӿ{MF^$D6?a;Exx8-ZiӦ/͎׏A1f84;co'Q2ۯ3okRѯ8/l7|>M?ωAOݺA!fl߾;vcRN:uD:uU^^^zGB!raaaR|H=]j#bGu6 (w6RS  a,X_]HBܢFԮ]3fHQR 6d͚5ӡC/L[L& HlZ.)LO=_6ug}Я_?&Nqx, ۷g]ٳ'&={ₜw hPԃi* doǶ9pz G#/,lYBQXa;a4e(c.&jv|AoB.!!۷u֬]/_d2QjU֭K:uSUTq BG}v Ɔ hҤ #GbcvjgDѸ~` Lgҽ$*EO*%j]U};ob뎼z*&@wq/@ll,/2111oԭ[WHBUrr2ŋ?;Uf+-Z0| tYfѽ{wƎˠAP,vWS-ulǮwb= 4 VKi_?Bx;v3+{P%"4m֭[ٺu+BUUJ.MzSkצf͚Y!s}:{;9Ɓ+Аm#q| ():JLLd߾}DDDζm۸r fիH@@=K;B_2o<6lm۶;VjWs)ki6ɣ :y $!ߤf͚κu(Qޑoߟ حX6m0b><#G3|ڵkwcO4D{S'J^.@;Q()Oѱc: r޽/4lؐ@j׮qBգhѢzB!MΜ9W_}Ō3eСr~̵T#TAg OΏ̲i&ZlIZX|9;eΜ9ٓ3gw!O?QFpBڴiwGi!!!XիaT_p*тծ(R9W5W>2()Ǖ+Wزe DDDk.xg $00 PF fqBq/_/d)RÇd;d+%bPФ8( poJ _~*¡˨Q0`qxb/ks=wGF-a֭:\HNDjE`43Np,BczꅓJw\*vd"ǡ)@χo͚5={ҫW/&O gk ׽{wرcQxbTU7`ƍl޼jժҰaCf3xxx)WHIp.J͎AQP3K7<С}B<_t6󌻙bN\Bql6{!<͆U!]Y~r.<zc2tP̘1c#cFƍ9x,by?~;w:|'ORNׯϲedclSllbqiG(H)G<7ʿM]q5'7 B܆f#222k #ԩS'k'dÆ qqq;B!i,YCriuƨQ(\#laM.ZAMy RaPx݉(,oG4 ?~<ޑxTUtt҅/B8B}5k=ݺu;OСC?>ǏV"׋^ztԉS͞=nݺ1{l:ww\O,6ظj%bCs;h \q5fRBIٳBdjj*4lؐ&M!B\d :?&M0aWw,RRRظq#/fŊG۶mi׮~~~Y[`]Hr9ņi ]<)J*~PՄ"nfRtBǎYv-K,!88XHB<1i&5jw!˗ӦMNJ>} ɓ ' @8yiĥىظjs-F=}kP_yюD4l ogFO\Iq5ʥY]~_WcgG.F>& )bEvPŋsy֮]K5$S70bΟ?qx4M7 ,,;w:1׮]# *fƜ#KKkħHIHohS, )VFCf#w2l"JQR+JtttV;u&d&Mh֬J;B!4Mƈ#pwwgĈ2A N|q=hLI$Y\>զ0Up'EK!_ vCAh wSƽو9~|RB8cǎqF6nεk'00 J/{!"4%K0d.]Dɗ/=;v+Wxblق'/"/2[Cl)63&k-vvl˕֥[4(3g L9T>|f#FDEEѬY3YnŊ;OK/(ZJ(B<5jբCL:U8lʔ);٦jW(ME6 ;ico2( o.FKQR0.\Y~=;.\ӓFDPPUVEqB!x|nԩcǎ}֞Ɋb,Zh *Dpp0m۶Yf899RN$[Ul*&k xXIݘ\hLgwSI&GB = IDATēcZh?+Vx;.\HΝ9s E;OҥKi۶-+-[;#k߾=DFFR`A'$ͮaXԌ{F=`ikfMӰL2v 搮$%yE#}Oۧ3) NFNFWc9.cZ}()ȱRSS믿X~=6l`899ȋ/HPP҆M!>̰aXx1M4a„ T^]X6TUe˖-ZKSdI^{5^yyL&1a;Qyo4ljz}fXM[.m7]nP٠`6( fEdH%}"h>!lܸVZ /pBo'ģHIIpŒ7}GG,_HJ(wGj֬ɲe#rM#k~SjzϪj?{weE@$YEУT  j'424_KOUV1xT\2wEQAYg{?TV3p}?a\ヘthez+_^V]Ke2(d2j\uZ$"r۷oǭ[Ν; YfM1O?Œ%KаaC̛7XIIIeׯ_@tAڵk1tP 0+WJHƍصk(DTaa!Zl WWWǛ Gǎ#GCD&$ h{ʕ+]6:uTVBzyyIDDD&-[[[̘1Ç7obAVc˖-y&~OtD"5vX|W˹T._ BFFE!zѺuk̚5 &MܦO Ǐ8Dd"XJ3e4XP(@׮]ѵkWlْOʉ$_~Icbڴi"..ظq#`Ӈ/F1w\L:3f̙3E!2)prrҥK1tPqyaڴiHHH+":Nz D$=yyyصkbccm6dff={D=ЩS'HDDDo?ıcob޼ypuuh4ZΝ;a0_6&:"ɐ$ >/^K$C.c͢=sFݺuåKpQ?K/sLKI")))FLL _|AAAF-w=ٳgCVs/дiSѱTlڴ 111سgT*^}U!$$#VcƍXf DG"2YV;#l/dƵkдiSXx8駟bܹ8z(6l(: R*DQQ˖er ѽ{w]vDDDd&rrr駟bɒ%hذ!ϟ={Um=LV#11ҥ лwoڊHd ѷo_$$$_E׮]EG"2i999puuO?~C$?z \t:^yԬYwQ5RZNNn݊l۶ Cpp0:w:@TIDDDf_~%Ν {{{̞= ~|Xr% ": R .`ӦMؼy3`aaΝ;#$$AAApqqԯ'"'''OƄ PF ѱ шD_Ņ P3(##ݺuC~~>v؁FDd6/^>ٰHϣiӦ5k&N(:N;ׯǙ3g(: RY111HNN:w f͚#;s &L;vo߾X`<==EǪ VV!,, \b)k׮;!:YIOOlق q5k̙'NGtrχze˖CD$hZ޽6mBtt4Q^= $$m۶YDDDTn9s&,Yf͚!""cUyňZƖ-[pM!,, Df-99={bcc9 BѸqc\Rt"aZ-7oOOOl۶Mtro )) 8D$KI"pY֭CvvvAAA}P^=ٳgƏӧOcј5k$Pعs' &"DG$R6oތGXv-#*SNqHJJ8DB9[lٳgرchٲe)Ya)IT'O!DUXrr2V^#++DDDLEGGwEvv6&NSRt*ɓPՈӧ=z ,, ݺuDU$I2e ϟ Yn Fit:4iVZ᧟~f̘Ha)ITŜ;w?~'\x7ƛofcǎaؿ?yUt,f4미p<==`tJRtLj`0`ȑl2 6Lt$*I6l@HH8Dmܸ}ѪU+q͛q0sLqa)ITܸq V^D",, aaahӦxDDDTMܸq| "##Ѽys,Zc{WȀ@d21Rضmj5z):Q֪U+믿$̙3)))prrDf6mO?;wzѩS'( XjMJ3gbĈ墣bAVc˖-yfDZAAzÇ#::'=}~;wNt"k.t{EvD)ԯ_Æ DKI"3sIX|9n޼;bРAݻ7lmmE#""j&>>..^ &`ڴi<&rsshlܸEEE@pp0ýLDff&z职 l߾͚5Zؽ{7:uꄋ/Gt"бcG ۷Otr[`>SAt"d,%@nn.֬Y+Wx0|p 4΢Q5>?3^u|嗨_XfC 66j;w`DDt˗/k׮عs'O zZ-j׮ `ȑmwFD)BxyyE!JRȄ%''cٲeXf F#W_}z1ch"kcTlڴ 111سgT*^}U!$$#C:u ݺuCZcԩSGt$j^5~QLF`ii;vRn1.]T؈CD$իb \p-[Dxx8x ىGDDD1f9sGg} %%PHLL=t邠 .Od<={qزe O d…OhP(D!2 ;v@q!jJtrɁ̙cNJCD$HNNFDD֯_GƋ/(:Us999裏|rt_5^xѱLɓ'VӧO=z@XXu oGhh(:v숟֢#U[O/:hժVEG)cb۶m8>_F$$$`ʔ)hР4i]t]Vf!Id&֮]=z 44+ I"^xa߾}ɓ'cÆ x(6~x\t ۶m*'%v-[H#$$Zn-:ȑ#=z4=ѣGc\JIIIPPȀ@Nd"##bرꫯ8Ad" N͛7Bd2 4hP|嗢[.]R+: UY=C ׯbHIIATT I"""2 ?~<^~eX[[ѣ`! ǎØ1cpA;5kj֬):0Pعs' ~MtD"@ZC fDD `ȑ~III?EG)O?HKK@QuH*X~~>"""닉']v8u~GDDDdR0~xj :IIIdjj*"""ХKaԨQ˗#'' ?~< I*!!!Att4 I"T*]t"oɓ+:J 2۷o*'%*HVV,Y  6 'OF:uDG#"""zZƸq0c 7흖hj$&&]tAPPz [[["((gΜ֭[/:=(={Vt"/_ƲeDG);v_DG! R0{l|x0a9ZN;s F?DzfN< Z(>}ѣЭ[7XXXHD@FFw<ܹ5-[ 44999Xp!>cdddu=Ft8::CDz MT4 >4h۷oǢEp%|,$䔖/֭[8p,YR_3HHH)SРA4i}t]v!##WFpp0 Ij"%%m۶NCBB I"3/C$$''BdrV7Rn}J$QIIϡR0qD?VVV=Ծ}0j(bƌ8q" X`0 )) jjAPPL&:& ={ 2Cxw0uTQL믿[RnCnn.v%: U DBnj3PTTwy}"dbʔ)X|9^{5l۶ ^^^cUbAVc˖-y&~OtD"lϞ= AVi&ىDDOᥗ^¡CD 2I !C '''qʥO>8p`Y|+ ,_DŽ 0l0bΜ9,$dj4j] \j < EJJ Nɓ9s& I"–-[УGt [ne!IdXJ=ZHH,--T*EG! [#11ƍÉ'iӦMt,"""Gx"Fx1ϯR'Ri4BVcΝ0 GXXc5"zի1|p1y~69۱cw,NO=Āh':JB#&&Ft" <'zׯcȑh۶-q||8~c0w\4iVB255ҥ 0j(ˡhXC>ҥKYHUM68qBp"{"77Wtr E||fϞ l޼akk+:*0p|Xr%>ё_$#ۣm۶Ub/FWWW4m;v*Rt"bٲeܹ3j5yѱpY 6 `4~! jcǎ\.w}g'SF$&&"&&+.\OOObѢEСJ>e!o&mۆ͛7gϞ#Q%hڴ)/: ̙3x{شiDTAd$IC{n 6 oЫW/ёhѢ.]^P*Bz{n?7%L VV!,, dc*((@^paDGGM6#Q%;֭[P(gGRRogt)))ɼO zJ%%%9s&ϟݻcŊpssІz_! jHHH@@@_qq1Veܼy~~~G~':"Lسg5k&:Uƍ/_FE!295;; R T(Do޼ør ի^zaٲehݺ5r.BDVdd$}]3 .UcGzz:JJJ`ee%:I… EǨ͛7ѣGE rb)IUhĘ1cb DFFbѢ#U[z#F(5QjPh|4=S r)Dzr`ۓ22.,% \kNcK++4k!0v6>Į]ʦ#JRaϞ==z4z-ZTB"gܹ:u*f̘3gCDyzzB$Wt"+ ##iiipww\7oŋADR^#F`X~=#:Q3J(P3Po@ވB"Rk4n( d0S=UW+3^Z2qmjVبxihr|BRNωsB.R[6JlUrت]rI:^z!++둅$iYfqOn"F$|XhDD& R^|Ed2?~J̄8DXJRxƢSN#U)%#ni 3@g@{&e2Hç5-X.Ra}ze-c}r9TrY(`R}lxpI'-0YYY8|0^z饧CbȐ!ذa֭[0ёD8::W\d^{5qʥy'N$2c,%J02dvڅ;wu֢#7K Wj@^xpv(vǪacшR#r;a`R,0Ĩb[l?O lܸ$=s۷/ߏhtUt$"21HMMȤ5mڵkٳܹ8DXJR;`Æ a!IDDrKQGN7='X;&_JQBޝ;&bط~o}/\BQQQ*<3ѣ"((gΜA\\EG""II'hڴ)6m$:FhРΟ?/:KI2{NJ+aꫢRzSG βFoRuJwwҳXu/ z !Pp+^ ; P\$bPF ? U޽;򐘘FDD&iiic/^`(;\5lΝʁ$M6aʔ)/믋CDDd$ pHBnn#(Sw'UU k;\&S@KVj TADPJJ v $$$Ct$"2a...HNNȤկ_HKK8ҠA$&&ADR֥K0tP18DDD&EgQEF9%z2 ,!Hޥ_ F\ťR(d28Z)fc*PQ%JNNFϞ=X899DD&YYYc4___ŋ;lذ!._ NJ%:=dz=|Mxxx`…Q"ҋ.AdA*+ kH{;$!Db=d2 N*XZ% J"@{믿-[bͰ̀3a4!E!2IΨY&.^N:S.~:WS 2S,%,͝;ǏÇamm-:P9%z\UE:DOK$E:d .5TDm+> ٲe  nݺaݺū z=nܸGGGqL.]$:FթSRL2;.\̜9/8DDDB tzdQOB"-2 Q)P%XpzիWc1b"##9DD]XJ=F:up51 r颣Sb)Ifރ/&L : 33Jpnh,$ Iz֌ws:Nԍbԫi _{+=IDKDD&LI&aΜ9[DNBdpU1MRɉ$c)IfÈŶm۸1U+IBJ~).,AXBI A/Kp9S I"z$I2e ϟ>T͚56777- In: 6 Bt""Ju@f@k4F3_šbp :L3J@шVDvp ޽$U(;;;ܺuKt "vy$II"R‰'`Q*Uz5Z> rSS6ʧ^IpLSk,&Tkyyyؿ?^|EёEAAD&*,%KI2 prrB-DG!""4Z)$韓rvapJ/:?_‚DJqDS|m %:u mڴJ+:UA* :Nt "VfMUAaaDXJY8~8Zha1=Nr6X=-C^KEGǸ[c;x ڷowwwݻu(DOfii j7DǟۋADDTiP3fRN߁C]gk-ho֬:s[#2ҭ?[W8R g(7Zv6d)C0V2^"w]h81# |*dzQ 3 W/Ev7~b$ZYnC` edoK7|8Q/W>NlE!o茾CӖbG[Vh?kEZDUкu0dV*{ z2 OߊE$&ωRSdu{ʽвo4m3CgO bݖX׽> 07&=9,+^VW9׭{G|Lwpc0I_l_-磎h+rpI`H˻~;bΰف6Aߠ/[usGP$4Sϐ  DU͒%K0p@5 ? I"z&XJ='%TKDOVTT1*NYI!yo˲ |7N 7=ovgqk0*PC۞p?Uk~ pn Եzķ3?ɡ?c kCu:RBqr".>9J1 <䉨̝;cƌ~EAQ*o%z2sRȼY^%""DV S=,PZ\ õ3Z53"O17j_ݠ Ornk0|}dDOHӦM÷~9s戎DDՌ GT}5V"'k%&z.} [>{ϫ5\_QC-Akr22Fvmz~NBۏ_$A Q0gR{4\X/Hp}x=ːQlEOqeshS2IKi1(54;+">&hr~yjH]# c)IDDDd=qhN8¥ &)L_^WS"H70\4(<~z6REJZ/2{4JTu롶5e! nB+xZ|)ﴘ7Vl)0CiM…āwQH:#.[I4 DTEEE Ett4ѯ_?ёd""""`RW KRV/]zl-aY a5,gþhm9w)Xi7?Ī3kNnzH{t .N[?"#֤`4\;v^6,w?2@ 3A#v<>*E^MYiPc.X٠,9"~mUOM_,=~y=F|oZN7sLh'c,pz}j  } a$LxZV^nn.qiڵ #I\H̀L&?̳oJu Fl7%x݂Mw`!CqۅBu4 222н{wh4رM4EEE NBdڪQQQ߿?$2S$"""$Avoh}e}+++X[[1u i*LXhjBLk׮PT8p<<~M +-%oH՚2X+p#GGDll,DG""""""3RX6Ldgg]vcff&n޼y ԨQ`kk www888(**V-+˾R5lmmQN8;;^^^Eƍ%N#ӔO­`Ǐ v. 1ó+ծ'$޽{-Z`ӦMYHDDDDDdXJY["feeڵk.+^, YZZ pqq#ݡT>+uDDDDDT==-U:!??-PNlٲڵkCRh4'Ο?WbϞ=q Ir١VZhР<<<&www<_#Y)hV״ędj!`TmdA}qxBMѼys4n>>>WrD"""&LI0gq `)IDDDOh4"33ׯ_/:ȸoׯ(,,wMuqq^~epsshZdddիz*Ξ=}!33:lmm䄺u떕7<<>>hܸ1#z FUVaŊx뭷DG"""""*$Q5sw?{L\z@)[eu-+KKKWʕ+HMMERR^\ze_RA.CӕMUT*xyyM86hިWPvgH$\x8r9dBPaÆh {~Èk].JD28P.*d^mڴA6m0~x[pq:u 'OÇqahZիvbccq HJ9=R 8PիHDDDDDTŰ$""BrsshLIIyheeU76mnݺ}~{effM7=z/_.[^ʕ+,hT(eNeQJ_>|}}]V:z{{-Z]v eoFB@FвeK?r fr١mvP:%OSd1 8s ˦q (-[bEqq1>8P*h֬Ν;]v($dn5oLĒIv1RC gkUP=E$* 7Fƍ1x`ޝxeb$ĒM$B,I ji_U)UΗ.ZRA QĚMvľHd_ffTXLq?kܷϽ .hƿ|3|?R[n 4ŋXAocS#ѹ9J%%..Nq9j5`Q93Կ4571̌ibfQ-ޣCtAϝZ&==Ǐko5jD^䭷Ύ"5]ɇܹsjLMMܹfʊ+{[)))иqc@?ȨjI#V+gϒS5i֬(Jr W\ H.]T mllRs8z(ǎرcQXXHÆ qqqgϞSYw|u~W~W8@NN:ubРA㩧K9%J_D 04\6C߻` iffB3#cajwY;GTҦM ӫW/JJJ8sLɤT*166JRV...XXXh Ȏ?Έ#ԩ{y.I!YZ֭[\nݺ#IBI)-jk׮qy.\EoKKK5XZZҶm[LLLdffrYΞ=[Ν bccCNhժp.]鎬j߾ ;TݻwILLܸq###lmmQ(ťRȬR#44 bbb055oooQ(Z|wԐ_$T{aR%wo<ٷLNdaaX ͌h6l̙3SUj(,WRT\y,(XS%68+usg`Rؐ&+?M~~>j*lllNt[~~>CEѣf䫇M6}j.\@zz:ɤBjj*4i{{{Yimm]oWQk׮eټꫬ\RnRB je}U:%mJ /'M!ēT*|2Ν_~]sjaa5;vu֏UKYYիn:еkWlmmܹ&lԨfΛx tpp@Ph~,#ݵk8q&|ۚ"P(888mSNNc޽pM,-->|8Ç[gKMMeܹݻ/**Jj*J*J~qqr_̄Ռ U~jZk͘5?9c̍ 17hfd!FkF8n5??5k 6m+VqO@zz:DFFř3g022ѦM};+SRR4c5jD.]4wY ,_wy k!x$je}UBI!tB'ISnݺ@Xq\p2ׯOǎcǎ͛7"qq299bV_Xyqq deei~G jN8c8z(ǎҥK`kkK޽5Kݼyٿ?J|}}gϞ:=͛_ogϞ|縻PRSRQRS\FVk>O4jP~ToiP @!#00#*/X414420M 05sSCL 114:Izj-[FQQ 4`Ŋ̘1C 67nرcnǏSZZJ6m*uRVk?773g] Qe}UBI!tB'ISƍ={3gpΞ=[ijzJǮGKK*u=wrJ){T*LLLӮǎ;jIMM%..NsǓУG\\\pttqJKK%&&>̕+W066Ņ~憻;VVV*v]8{,[ߟQF1x`̪ռ4f̘ÇyYt) 4vYJXp!)//_~رCnzfšC$''-ZE8pC._Lrr2IIIǔ2:t{t gggҥ#,t[aa!cǎ%""۷풄A_W%BI()t4Wj˗/k?yyyaccCΝmll-&%%ґ˗{_ppp{{{MiddTJKK9}tȸ8 5{Vt) zNMDEEMtt4EEE4jԈ޽{kq*(mƎ;r ]veь5>}]`YYXx1_WWWmU#yL6]vahh /ի_KT*DGGJXX4lؐ>}퍷7=zx)jsiBʄN:EFF嘛@qrr?իJEBBtR޹s͛ŀ~eeedddh΅!gSk=p5 ƭ[ I% !D sB< *MBI)DmWZZJfffNNJnSVV[ܹ3:uعsgZn]uhAIMM%99[nkvx/JVqⰷ׻n@TOTTDGGsuׯO޽77Gk:t@~n޼cǎeر888"11DN:Ebb"cddD.]޽pvv~B{233:t(&&&۷OuF]qB6 %NЗŝ;w*X]X1 vRӧILL$!!AzE5kFnݰ׌^uppm۶ϟȑ#;vcǎKQQ7Օ>}лwo\]]ejTVVFbb"qwь7twwOO'wZѣl޼@] <3mKǔ)SPլZN_"V^^o| 4mڔyo`nn4JE\\aaaIQQ;wۛ3p@7oD_\[RV>SN@rr2EEE.Ŋ=*j߾C$ҰaCw.|98veee퍻; ze׮]lڴ06mȑ#>Lj5M6ՌֶN~?~6mԩS8q"_}.I!B焨}Y_PR&kBZ/jwBHLL$///zAZSNɓ'9y$iiiT*McϞ=8qCw?/??"""cee+VCkU^QQQDGGIII mڴÃUV|MXXaaa}:ӧOÇkB˗ӠA틯/FCZOFC7/LԵzPVV3f`׮]̙3-ZDxx8|8;;kLΜ9þ} ~;;; ưa^zZϮ3(,,ӠW^uvDRɬYoY~=SNvIBQ+U/2U&B\BJJ j:o޼ vvv3qDpppSNn- RTT( OBGfggsA'55###ãB/|Jll,zߟO?5jد%͛j!/^ct֭ÿWi&+Ξ=B/瞣aÆ5Z.g4i҄cǎѭ[7m$tܔ)SׯcǎGűm6.\-SLaҥjJ*2664Vʕ+ݻ={pB̙ #GϏ^zu1NNN899iT*kǏwinJ޽kꖟϘ1c8~81B!B%[NЗB7瓚Jbb&xLJJڵk[n8::䄣#ݺu{ؕ !cbb|2FFFt֍~J^]߿P"##),,K.x{{3dH&M뉿T*5...x{{퍧V*++#88o5kĉ6m5^XngߟoVB Y~~>/2?3?DT_tR ?>s̡~.Wҹsgqpp@PHǎub\7"::#GKII -[ 777틫òÇΝ;xfQۻ{]!!!FNN;vLJaÆ1x`ez56nȚ5kr  bҤIhm3]T*Yp!|&,ٵ,bU={6l޼zK>#LMMYp!jO.Mff&AAAJ ???v(++#11(MPZM6iӦ./]pJKKٷo]tvIBQ+U/J $:A_NB{^zY\\1VVVGtj3gEdd$QQQddd`ddD۷&4˯JDD帺2bFAϞ=%,A3,66VgXE7Bjj0{n7oK/ӱjm ''ņ d:]T*'N`@Æ fذaԪø8֬Y͛122W^Iۥ锤$FAFػw/VVV.I"֣QT,]K2sLJ#[O8 8x |gt]T1500ÇS^= D@@O?Ns[^^Nzz:њѯ)))akkCuS[R&\t_ ۇ!2n8ڴiڵk8q'Ojb7n榰~=_~cرlذA&y!U/J $:A_NݹsJdRR%%%ҹsgMP(pqqZ%33Fxx8YYY4iDB8''ٯ)''ݻw144C'AVV+~~~^B<6oNFF _׷ժ+vs=ǘ1cjUdOz k׮~]Ri&{=rssY`ok #((_~Bz聯/=.񡔔pI9Çի₻;^^^xzz>k2{l^yVZUkBH焨}Y_PR& rT*IOOX1ƍkgggqqqŅΝ;Mnn.g={ tqqqٳ7~ 2dN^'iiil߾ݻwsqի5Adm ϝ;ǺuX~=eeeL07|{{{m~G^|E͘L vYz2_ѣ`ݸ?9^? h"MWv aΝs3f :{x"9rHQT鉧'}˗/wa|5XBje}UBI!tB'I.zj=z)((ؘ]jƮ* \]]rR$>>^/dDDooobطoCeܸqKYÒضm۷o'))VZ޵(22+Wk.ڵkk/,E3g2w\/_r,b=9%%%L<]v3~?[nf\\\߿ W+p~~]`ggY~~>G!**h"##)))M6xxxٗ`ܹZ5k0c -W/I焨}Y_PRfbɓ'SSSwB^@PЭ[7LMM]v"$$={~ܹC aӦM <͛hMjh6m֭[cȐ![QFITÒ $00Zh>|8ƵTV ?( [&LP%W7`ɒ%,ZHزe ŋ̛7kѢVb̛̙7ʕ+qttB墦hnZz5&00cѢE888uB^hll瓛KV055ի,[_~Y˕ !B!DNIN}tJdtt4wt@޿c^077vJV޽{ٳgǎ///Fx"7o믿̙3)S`mmꪊ &-- KKKF~O>Txw۷K+֭c֬Y,_OdȝO>ьvphh(o6L2e˖=~|B?T*M@ȵk4+>NwPV(++#**Yfq)((Yf3` DeBT\ Q5*B6icBT_&&&tBŋLYgիWi۶-#F` 2 V\\Y~=nݚ &0ydwﮕx~'mƹsСcǎeÆ G',))7/_ga֭T 6n+| IϧM6L:\˿ X9y$_5/f۶m{z=MA<<<<5y"_YYYDFFһwoN:EDD|Ǽ۴lْvB!BؤSR}GWT*tNNN: 011v5*''Ν;)(( 닻VC4֯_ƍ{.L6!C.<}r%~'~aܸq<3KUݻwٰa˗/';;qƱh":wygy7O]ޓ;Wpp03 Xb| ۷?5TJ%lݺ;vp 7nǏSN.J_ΰaøy&O?/33Sͭ'ȑ#i׮] W/\ Q5*B6 %NЗfmljɓpJui_u{%00T*qss# qѦMWRR銌CL6SҺukVܽ{;wHHH1ydOYv-̞=[*V={6}˩d8pѣG3dlق?˗/?0x`oRh(J""" ddeeƄ 7nO=KSΝcСo>IHHЄbccٻrС4nܸ߁B^r='DB6 %NЗfmPZZJ|||sssѣ9r$M4vdee߲zj_>}:cƌT*ظq#;v젼!C0ydFs+5kРA̙͛36e3 6Tdf>|#FŶm۪}1<<_Tf͚ҥK%|{Pnڴ͛7SXX'O瞣aÆ.$|||h۶-{}{'440011͍M>}06Zu\ Q5*MBI天 @FGGEll,EEE4j'''<<>RI6mP(̜9WkjJoƆ عs'x{{_Oך_p/ >㸻k.|}}eaW_QPP?}y[n'vZ4hE5k|AJǓOhhBۗ{2|py6o\ǦMj*f͚śo'cǎO?|nhڴ)'Of\|۷qF %G_GVæM:u*?<_uS֖9sPXXÇ ⫯bѢElْ˨QdB!FI r'(((ѣDEEő#GˣAKd>}dD?p}}ϟo߾L4Zh*#}bcc9r$-Uۥ ~zBCCҥ /SNq|Xsssϟ/a۬].]͛>;WSO=eVDDD0l0&NȺum۶1w\Yp!s̑o%&&?yf.] /&L੧zb_0g͛G}Tkn^JNN&88PQT燷7 B% !#NI!F_WSR& r|7o$::H:hFGGZ{n6nȯJƍ;v, .*[l2RSS;v,NNN.NHIIشi=3f0`Z(ذaK,ݻkҝ%?#'NdÆ .N駟x?#&L`Fͼy9Xb˗/ʊ5k0hР'\7*H~nJQQ#F`ʔ)1 OVtR/_μypOέ[طo{a߾}dggөS'FرcݻN_! %}Y_PR& r;W^%::(Mihh&cǎ.U>}kߓLj#:uc-8U0ϟO||<< >.NbٳN:1m4LD'AVm6.\2e K.UV.ΊݝW^yO?Tlْ [NI-go߾,_\)9ؘQFI YCy>Sϟϖ-[yڷoƍٽ{7۳|r˟`B@PP.\`$%%1eBBBpwwʊ3f$_SB!B"5@ӁG-֭ڵkL2p]N8UYf.]ܽ{ӭ[7Fݻwٲe OfnZ%W/ӭ[7233 t]ۥ鵒6nHn?~| U*]vX`OĉxxxG``?#440f-U8::dRRRHJJ⥗^"** ڴiɓ%B!B< %YYYl߾7|WWW4i'֭iӦ,_drrr8pK,sssm.^믿N۶m;w. "%%(j힛*cooOHHk׮ѣV5eժU0}t\]]INN&((~iǖ̙3Q(NppܵD`` 7n믿m۶.N>|jaaO V$>3|||ٳ\cɒ%$%%Ѻuk<<<c~Gڴi[oŔ)Svgƌ3o<{= ۷Yb_~%fff̚5ٳgӲ2֮]˒%K_>/fڴiGqqqaҤI^Zi/2O&&&⋬_^KmxxxP^^Ç<((W_}|/^ٳ{x$||\psss4h@XX.Fm6mFBBL4'bkku))Dժ=%mq!Aaa!,Y!CЬY3 B`` -[Ui?H $D&O#X xZHd郉 ,[Lj˒%Koeɒ%\pe˖M J=?>'O&--ӧˢ{-V>}:ڵcŊ.Λ0aŸp-++c„ ZH.ՠ >$Əφ kz8S-u!(n\ pGU*`RWN\EVV`2jm@< x'O&ϝ4nܘ۳zj={&uLAAAJqRCaa!xyyVܺu+cÆ ɺupppRJR8W^o߾4oޜ7nꊾ˗???177:ۛzˌ3x!ӧO@x*q]K=[.wLJ?XhŸl޼SN/>PtܙUz###\~4155姟~ѣxzzlMxx8ZZZX[[3k,rrrT6a K.ԫWHYt).\UVX[[OffQ9^^^DGG%3gGGGN<):AAADQRH.V,_#~ڵkcee%a"֯_C A.|իw^~Μ99*G(֬YàA=z4=!= мysV\Y=y 姟~RϷ   %8)|08y$ԩSM2gpww',,v_MFAp-[d…L:0nܸ=SҬ_>}гgOiժԑ4ֹs簴LJ%Kpeڷo/u,)((KKK"##9y$IMrSү_?z%u EWWÇKG Kmsu1b#::Zm ƍ ??aݓxxxp9ZjE۶mٸq#jJ^+W#GrʕoiР+V   |DQRh7o,rՋ .0x`xAAAcee%:$+:ujժ\r痺=J%3Sm6&̘1nݺѴiSnݺFL˖-5kӧOtMX{ڸq#nS([|PPPq7077oe\|YmTXΞ=Kdd$M4Ktp}J%ӦMcΜ9Y?.744dԩܾ}gҠAƏ)3fǪ ^Jtڕs=>SΝKݺu"##Cx   LT*!G&{nyΜ9ɓ'9|0qqqѥK찷zŔZx_vbd2~RWvv6Cȑ#lܸ!CHIcݺu!C|||9rԑTӧL>>}rJԩ#u,ȠaÆ899b |J% %*%\DuobS_oeµe2^G&CK:Z/.KѲ⥕BW^DGGh"-[F֭Y~=M6UBRXX ۷o' @mZHNNfͬ^XuƤIׯ_ڲ@7EFFƍcIŊdddЯ_?n޼IPPRGX?#ǏYf\zKIرcfϞ= 8PH¿]ש~ J JJ  J J J/y)W^){QxwD'Yz m-2BL =ђR ]-2X^Ŗ-[dΜ9j/-[ŋW_}EVX`ӧOרn|Ͳqppܹs8p@oWZwww8|0|4lؐq1jԨRjȿeddx{{닻;&LlٲRGAAADP*I$Ο?9tԭ[=z`ggGϞ='jKp>sؼy3}:ҿF>}x!'NRH)77www8q"K.EOOOX*;888vZ*W,u,_̤nݺ?HP$G PAv<\\ ~E* /ʗߩAJb<UMS/?)Q~iiQF[ }e(Geu^\Lشi_}!!!Ŷw\.gٲe̟?͛e4iR,s /--~q->,W^e͚5l۶ ɓ?]FFWf3m4&OA(D ))BI BWXX.'66ʔ)Cǎ+++c M;v,lݺT/J^ɓby75{.DEEa $u$:r...r֮]K$=<~J*IG@v9rr d*.P[2^(]`TqqDc+QFGrZ|EYm tHWr:Z{^T*ѣOׯ!&5Pbb"{&99ǏӬY3I󤧧eVXAttkJJ ˖-LJjժ1{lF%~Ax(J EIAJ-›g144ҥKXYY{n ]$KB3=z4nnn8qT$ӧ\pA$dX[[GDDF$3f }CܼyS$K,VXĉ5 )W*I+$&3;9&gq:6QMrR&Rsy<2 $E=NwI܂$}{1*P*(]|np9)tBClf>iyoK4dY{]s7mڔ_ϟ w) }Zh   " zrfΜIƍ333vERR믿:ңG9~8RGOݻ7iiiQfM#iӮ];lݻYn۷/={ۢ;RC|rƍqޛB yD>LGs,: ~C\f>_|TQ*_a֭9x !!!Ӻukzkפ&  ;vɬ_>}PjU Ƴgx1;}m۶-y clmm'88333#' 0N>-lU&Oqvv&$$DckU*СCLMMz*:bk׮RG9sɱ4~K$2=¢=^4_J%y<|Khr&Ǣ8۩9<-, {{{z쉫+ tKZXXp%ϟ7|CNwdym6#{L2RGW_%88z1rH5jRGT;r9HIIe˖8::~0  Bi#*@ZDEEC=Q&MB[[___ b„ Ԯ][ꨂPjU j#uF58uTߟ}L2ÇsAZh!uryINNˌ1BH*sIZh[8w#\C] ХK2(J t’Hʧ@,{Q] % Y%gr<&'Y$fxNIҒ#F0{lrss%ЬY3._Z%ATɓL&u,kР>>><~qƱa֭3wܑ:Ziii[СC1sLҤ'  ()GnbXXX`nnΚ5kh۶-'N ))[gQ\9 $66;;;*WѣGPԑ fvލq4%Mhݺ5͚5:J3uTzIݹv:t:&l߾cJr;-)YI',9| (UyY~+^t%gr,&SI˓n 6l YW %;;͛@.3f[֯_ԑԮjժxxx?s㩕....ܿ͛7S~}<<<Ȑ:   |DQRn޼昛rJZjŁf͚5]]] :uʕ+K?۵kZ {{{h 6mgfTXQX*KΝٰa?#[n?:Fw&??/+Dgq>9 e(DO ˦[BIlV 9ALf~wOŋ)ֹ^z+uR~~>92j(#+}}}y&v"99lllؿQG&*WDFF2c _>^^^IOAA>H()W ^+++8@BBk̞,?J^%((CCC#gaaa5)S"uqڵkٳg9v|Zpp0[ٳg\xaÆII(v_~{@nZ'ӹj =,p/{4Mˡf"--7ۜCWWwwwBBBҒ;wJ냒=AAAgII2888peΝ;Gʕϊrtf``;95jFoAAA(DQRxwaaa7nG( BOjj*ǎӈ}Cׯݺucɒ%R{ښJ*qU줎2tޝ6m-&hΞ=Z+:{=z̙3b WtԉpuZl3fffhta*U޽{Ջciiɞ={4cTAAJQ^۷AҵkWBBBbŊt--^ :[nDZ2h رcRG* sёÇsLLL ,u,EHLL$88-ZHD277' {aoo; 6ǧDUԔuqu6m;v$88Xh  DeI ;;={`oo)/(D oT*qqq:t&MHI%&Nȝ;wسg P2228p K.ߟ+Wj̞}GѣL2EH*@.]_ bRG$v]TVT(!IJɓϱŅcE˻ɈxI&?ըI&\xF޽5MU7nnnn\--V >}:>dɒ%ݻ 0sL>}*u333~:SLIA/_N`` ۶mvRQ3fQJK.Ѿ}{ʗ/OXXF$_hjj* kΜ9ʖ G$(QC܍ XsMc*Esr]x ,ط7baJOov ew|9m#G*Bg,׀֣0{p2C+uz:QySp)=gn6j :Gmۊa1rΜ͈R9GL[MGYcؔ jGƈzNzEn=ȝ9Շ|Ĺ_`"'~-t_qlBӹώA1S*_Ulٲ 2u֕e۴iCDDNNN 6 gggӥU"2j(عs(HJ<`ܹS~}<<#֮]ˡC e˖RǒTvv6'00_~'''# xŌ3Xbˋ㩅رÇS^=<<<͕:   h%蔄./"kԨ۷'<<7oNʕ6/?.HHH>}0c Laa!&LO>˶K>>>899W_O?Q\9#̺upvvfԩlٲ==9Jhi۶,, \/A+$p&~q+_.GSSfne@fN0 \8iJ2o^V& уMgyea«\:\&L^1^;.:.-6(bOؼ@z7k֌vﯖS>}vtԉ3gRPP ubF^x"'NwRG?ݝHFł hذ!>>>i>ZZZ888p-Ν?@Æ (]ڂ  n(YeeeO-&""UVȺuhժB윶RP)_ϔ CL0i Pd_N< KbxÛ3J2#|Y@NU؞T}MFFY~cwЪR h_n>m~ J }m1j(߯˞q6mڄDFFJ$&&ҥK"##9w:t:xzzc ̙3iԨ鎖RrpwwΝ;|'9mry  B9g59s&jbҤI4lؐ BCC1be˖A47(Y&NիW矩PqT&99MFÆ SȎ;ؿ?Ǐ:Jyyy1i$-[qʕ+ԫWOR2˒T_݌ ˫+І«_H%h^V Pxy>/<7),2s%3:S^k^rEWۃ@   G%K\?5bϞ=̜9"?T()m۶ϦMhڴqTj…+WٳgKTʢ:uǏӯ_?#Rd̞=N*u$r -[T阵 $S EU.@:yZf./^>o]ѧ3#xN ;f'Jv]jAVp3<Ljh"cyXLa=WfNh#CF +`P^Xu{|zwcHGSg z:k!A&E7 hЇʲL0/ƒG'[(RI}R1]'&Ay o!ȉ(#}Ǡ:o|} JP@_mWPO>۷m)4i҄K.1vX#RR7n`ccC x"ח:/ԪUuq 7oΈ#&((HhjѴiS>LPPQQQ4i҄1cƐ"u4AAA(JJ,##͋>ͽ{nݻRG(JW2fqpp:J=z၁qJ4zIhh('NFH*T*quueŊlڴѣGKI(QCV*LQ. .qt:~U=?ЭOOe\z'Xצ̙)tТ)lA~R8ms-Irn ߨמ-i=iO{={{n/u$KMMr>|>}tqS((e9 f:I(|QmSjTVJ͚5Z*UVUɞyyy3o< FѣL6E+uСC8::ҳgOvE2e$ŋ>}:.]b,]ڵkKK-aÆXݻKK4#'e3˟*00'''DYCJ'qFΝ;R SLaҤIT\Yh%,>J#GP(عs7o{nv% )**=z ^zRGRRɘ1cػw/z}Ա$dRR4 (Mk$&NBB NWWURzuWej011Ą5kRZ5޾>dΝ[422_~aL2gϲ}Rm9r$_|6l4X a޽ӴiS&Nٳ)_TB ,YfϞcŊgUAA r~'t[f֭ 4H~(Y|VZšC8y$UT:yyyѰaC(uRΝ;addıcǨVԑTjƌO??~j#]mlM>bb&9rE-Lbq:+{' yq8,#73jFFtn~MNN ǓZˈIJJB'VJWիWWN>}شiԪU8vd`cc_|AVXjC:?˔)S0a+VPIPd2߿?k֬Ã72w\Əq4h@`` Of4i҄cDzh"+  ›h+&//,Y£G0`!!!tQh(J7o2c v*ua׮]lذ tޝڵks*V(u$oؾ};RJGabb(E'CTFQ%r|Zn8 JrvqA!?n\>|][![7ahhXT0|S'sss#>>h㉍%66k׮qa^ChiiaccC-055FԪU:uPV-LLL4ѴiS.]_~%߿?T*Y` . 777# LOOWWW ƒ%Kpwwg͚5,ZH֭lڴsg<<<5jx+  h4dffqF-[Frr2NNN̙3FITf͚\p:HG#Ҷm[ʗ/ٳg5򍰫+ /,[LL ;wB >}J*II|||2e k׮E8B)3~x_e|~MRbI%|,㹪%m޺F\\qqq$$$CBB$%%QXXX ^:T^5kbbbB5QիWZjo$&&C||</ɓ'M\\kʗ_kצv|GqT&((/===mۆԑH.3n86nȺu5jԑ **ym6ڵki߾Ա"55V^%>>>%UJ*  %A8p ʕ+#??#GF͚5VNIsuu%::Wjqd˖-̟?_$FRR={'Oj\ArL2hOA?~L:umQwrC&oR@Ǣ4X}m-UZ\Ç_['>>ĢTR嵮?w_6i҄v!YbE D?~LtttWTT;111344Knݺԭ[zꕸ{+W>|8]vÃ3gz :rz IDAT{nľPv0aONǎ4h^^^ԭ[Wx*UR%|||;v,SNSN 4KRvm  J Ʋ|r֯_Oٲequue„ T\YhA%ߟ]viހ >|QJ'Oн{wr9gΜPH*}vF… >}qR*** ++bS_[ KrԯP;9$d DmR(DGz4Xr:dzJToMnn.,_^>|'OCAAAѿ)S (JCll6m޸|ωK2<<~"i*U //_~_V-IVGwwwN:Ŷm011),_9tvvvRGJ6mp9ˬYhڴ)̚5 *HO7n̑#G8x SLiӦ1k,./  I,122bԩ|WniA˖-QbcciѢ bڵRQf͚ѦM6n(u+-- ;;;RRR8w>̀$ߪZ*geȗ<(EqR(d2!GzԫǺ}LTD||knܸB!<{PZ5LMM166.Z6FRfMLMM)Sks#=zÇykgddojjZe@7oɉ$lB߾}><{}#=*^ 怒͛73o4?:vhSjIL&cccZjUt}JXp!W\AGG|uʈ+Ȉ5kRfMjժE5Y&kצwԬY󵎦Ԣ_N"::h͊+R~/ Ur\ e3qD.]J_ٓ<133+KWW?>ӦMc۶mѮ];㩔>sa̚5^zѯ_?uyAAAP5QRRRXl~~~0|&MDٲeDQR=.\ȯ˗)Wqf۶mѦMH899Mpp$E^LB镑AaaaY]WKFeW )9f哘]\D&Ш2 Q$üE+ڵƶmk ڶE|U'|”)SСe˖A4hm{]޽{3gwq5_I/!eWTDd0CbA3BF%cg=VcmdT,'klIvuwf,|<#?S9ݝ뾮OffUVޖ&&&ҷo_022"??$}6n͛8pwF֨QM5i҄M>[GGG "##ٽ{~ڵkٓJ*NKd]ATRFͤIpttd,^Z4ymիW@FŤI`AARI%Xj*UcܸqYB 8G*|7ЦM|#EMW_oVb/Hl L&cb/zS5 iB ϲ_8+')PꢃQޒ\}5 eoX_~I\\,AcoovvvmV|k޼95رctݿuT*U]=$%%#r9ǎɓ'"ca'gae hذ!;vSSSj֬uo͛ܺuH߿갬]vbefͰI& B۶mYf ÇۋwޘsۻZ(ymڴ̙33qDZhܹs8q]ڭ[7Yz5saǎлwo  [{J,V\>SNeҤIZU㱱ƍ4kL8^ZZmڴښjuɓ'ѣoߦqR8+Wˋ;w2d㨕Rd?~sΉA-.^H۶myf7P*yOJ+R_@WuI hV]Tx]Y^ߜ7sT֭$ia„ S* IJJÇ<|PU|'99f4h) 4 SSSLMM[. $n޼겼uIII( iԨhт-[ҲeK7oNJTrss>}:+WdĈY###?SNӾ}{8@ŊվPeff?O˖-Yr%NNNR*=bƌl۶}b ͥ%{J ›{J D租~Ϗ MƔ)St8mWJei&.Hddd' ++K,,,,--9s&vvv޽[4<СC8p [lNAxWFFF1|pK>>S,YB V#]ܘ4iVVV̘1iӦ  #44)Spu>3͛G:uUЮ];޽qJÇӿπSJ%xxx0w\h:v̖-[v{aСYOOOZȑ#ӇRxW@jN>/ryOn닀ttDRizT//Zy dT*Gq]"ݔQQQ<LFf͊*eTj֬?ϟ޿"'''/_< 6A4hЀʕ+CNNܻw7oBPI>}?~999S~}֭KJddggsu?~:fժUiժn+++k|…ヿ>nA;٬X P^=իԱǏb߾} 4ZjIKJ(J ›EIA4Az=ٿ?ݺuIKqe{0a2ŋKDSʕ+ǁ +~!q-TH[ljĨ&jWW˼^,s ".*A^txZR@r2Qo0$ФI4i%P(~Hͺuˣjժk׎۫ 5k|utӧh4Crhܸ?_M}uuu)_^Nm۶-L6Kl2b܂w aL2>O>Kbjj*u<211a޽3qD7o&MG  %Xp!?/[XкJIڹsjUJD8;;koc̙\psQzu㨕Rd̘1( n*^|%LC.u*w8RI+ W񪀗y ^^i JUL 6T[sOc\NzTbGE}ܿ ݁߸qH"""طo ,@TRn]W''':vSv҅+Vk7QjU$''s=^ʒ%Ky&FFFTPOBTTϟ'33U3;;}RZ5r9-Z(Sϝf177?~///,--Y`&MҺK>Cϟ7cڵXZZJMAA2:588ɓ'3_B[PbE:O<ʊC?JDPZ5VZg}&uB~ؼy3#G:-[ӧs):u$uAK1d Q4Vn|e+*Pp}V͟P OGpY-]JM)W 6HGbbbprrϏ3fHGbv͍| 5k0c իǎ]ijiEn r $xTTBtNKw?]::*(u@%:::K9=0tjK XS bCy=]KkD?~L\\PyX"mڴQ*;uD~ݻ 2tP^|޽{\.g׮]ԨQ;wpu.\7xG ^:nݚ:`aaA˖-Uǭ[$<< &hmNPqFMF5Xj)m()oF%AeKf͚Ś5kܹso^XA&;شiG%,,$ gRYP(5jueŊRQ4\]]qrrbR. R)FCDƎKxx8*?^fdspÍUJT J ( cvѡǨӠ!2zzLW=}](W􊾯?;>|8;w//T abb ...Ǿ*CCCQ(TP7bhh,+#߅qqq7>ٳg_Krr2}O>Ӵnoþz{Ett4ܹsh"##Yf˗~l 5jQ{iڴ)+۶m㫯"$$5kУG.ƍO>xyyG1tP/_NZ'  AZ_9x 'N$77 60rH l)#F;2uTSⰵE`ҥsY vcƌ!++;vhݞ@PJ.ZYfѸqcΝ;֣2x0o~&[LfcǎvajjJXX| olܸjժIԓdXZZbiiڟ2==V^ÇYv-O>EOO+++:vHpppiӦ?Rrev4igΜߟÇP( iڴ)M6ͭݻGxx8gΜŋܾ}[u;z( B5>АƍӤI[ 9FtttpwwGL<={2h V^M5Vc߾}_`aa1bAA2FkL>m۶1h VZE͚5%Q|{JԩüyS.\uW9W2{l::tP1[(DQqeƌիWa̙+WQPP#ԩSXXX-_VVIIIXZZ sθamm͎;gggիǁ VZDGGs9駟ͥf͚888СC:v숝Vw|[ƍ֖СCueffYbeZZW^%..X}6ܻwTΟ?Ozz:+Wss"⥙oЪ-)u!((` 7oϸq㤎v}[n̟?#GsN֮]AA2BZJ%䉌d)&ooժUlݺ̽pCBBߎ +K ___י>}:gϦK.Rʈ?%3gTPK.IR'!!!>kkkv튟RJ-Zjժ;wSSS ҥK9{,2k,T† ҥ ƪ#>6SzuܹիU]TJb۶mܼyt˜3g:t@&oߞf͚!ɸr ˖-w4k֌rabbBd…ݻ8UQS(\\\r #F`tڕ[nIK*T?gΜݻjՊb?,  K:%;v,111L2s疹6Eɷs|}}1cڵ:N|2^*Eɕ+WrEnYAAFE̚5K8BRXΖ8fgȑܹsƏ㳽ٶm(rcnncK58|0?ӧO}vׯ/u4.\d2E%?~ڛ2""{18::DǎU*$$ӣGvɆ >}:رڵk*UK.E.JJKK#66∊ٳgd25k_>*UBGGwÇINN^נA"`gm +ƍM6̝;Sj9#/^ߟiӦo>6nHͥ&  h1(J* -[ٳiݺ5nZX抢[?~< 4(Ś/bll{i3gf̘qnѢE+F %JtJϒ%KUŝكo̙X;wһwo5&ZhQ*^Xvww֖Cbee۷og :M6!WWWصkNNN%J*8;;۷osybbbdggSbEڶm&M===ܹ\.'!!;wUUVKKK5jDF(5)ر#/^dҥ̝;]va|1`ƌaAAPꋒ>#<>>e."!!1cpfϞ͌3J삖+<7 8 h6S յ.]<|"{޹s"##ٿ?~-JF>XXX[^y&7nƍܼy-[RDOO *e٠A4o@WW &лwoƌ̛7O&-,, ٳgL`` -Z:  J]Qرc5 CCCܹԑb&_~a޽;v+JGRwLXz5oСCRGQdfΜɔ)S:Pծ]OJD)JVZӱ".. >Ç5j&M*}P(( `jjJXX| olܸjժITdlْK.V"kcnn)Ņ T#_}}}IKKRJo^Usrr*Ἴ<8t{O>ykooOLL #GK.1Mv6Bttj4999Ԯ]{{{Up䫮.fffB<>|XdJ\Ξ={HNN^mܸ1Jw033#44 6/]zzzL23j(lll{M  Ԍo-((`ܹ| 6UVQrec %B޽{^|%ݺuc֭RǑTzz:UTѥ?I&9EIG FTTW^񂤜iԨׯ:Jx);v oooϟ{SNѧOʦMJ#l֭|dddh aaaˎ;ԩԑJ>rIHy߿L&M6"en݊u,jff&ٳ8pggw:N8iӦѫW/lR999rY"##9{,ϟ?{{{:uDΝqtt|s_εkHHHڵk\v;o[hA-EXXXФI ݻ3p4ۿkغu+M4: !Ʒ ›[AS2%%#FҥKŨ2&''GaRGݻwdeG(jݻ9t(H +Kӓ+r)Ν;G8p 7n, ZiԵkW=z4]ve̙3Gk_IiӦ 1TҒqcU222+WehꐚJ>}{.gΜy \mmm2daooR(__^~sAPP~-ʕ]vtڕ.]СC ֪UZj% /@uV}+wɍ7(((@&ajjZdJ lllľܜPVXY e۶mlRhj%w޸ӦM;&OcAAE;%O>ͰaèX"AAAX[[KI(a={Ԕ 6HE9s.]sN "u~~~Ջ#GҪU+.\AAvΖ  ܹsYd 2L%>dݺuRG~ի4i*U:Z|9s0qD#4nܘ4U~~>3gɉ-Zp$ӧFFF9rċYYY$%%{mbiiItt4#GwwwUF|QY prrLJ`={ŋo^:7n{TZRSSULj>nݺ9s ):::Jhh(vvvbYK)׭[\.ܹS5ζ{T\;;;|}} %77֒d4j|||Xn|DL$x]400:(((`Ѵk׎?\8ŋTZU%&//˗u/ ={˗u/9ܹsG(jqڶm˗/R{A777._̑#G055Uߔ\.|򘛛K~I8p TV u$֤IQLLL4hƒȑ#N}p Fڵiܸ1bd2ٱc;wɉ}ҦI&xxx}vILLd̙<}OOO֭ N@@'O$%%;wp!&NH e̘1Qreڶmȑ#Yl/_T{Ҥw\r֭[ӥK͛GAAԱԮf͚:tիWrJ:u5p  %yECT2c ƤI rR4HFFF=;w.ӦMe˖R8eSun:k*T uA^v۷g˖-ڵbǚ5kؽ{7NNNj?ېZ?d2 X[[.u,ӤI=zDvvQJ… r8;;ODDzo2"""EJGGǷ*2h  A˂  ܿիӭ[7K166:&\x .޸qT m۶Um֬V|<<<]6AAAXYYIXgq Ov)< 8 h6Spuu- ŋtޝK.& ?*RǷ~w}:O}022\@-F &$${{{.nZLNNk׮$&&ri:vX"/=}4?TK͋IѣF/L>)S{jٲ%ӧO'<<mFZ9s& 6___"""$<(W@DDűxb6mJhh(}fff1d-[ٳgɑ4_>aaaL6 ̔:ɓ']v\tIX  J(Jy4mڴJ1Q|=βiӦL4I(,tJr1ܤV]kݞ:vjݺpܹ3[l!((mΣG8|0UV-ޅ\.}Zjo߾v5i҄۷oKC޽KNx%nZ,2 [[[|||&%%ӧO3|p/hР666L:G~cM>}R vvv#T\8 2 '''>W^ۛ;wJZ޽;<~XETTI55((O#ߏ wQ.Kpp0ϟ?:~ƪƹnذAHŢf͚=zLVv  ؋׏sI̊{IA6 IDAT%+U$qKNG* (((NӣG*W,u `Ȑ!pppcKddd!Ynw{[YYY$%%N7Pة籶&<<\X%ĄW^i\]DGGӹsg6mʩSUԑӧOٵkѽ{wUѣׯݺuO>Wk ɚ"""x˖-___4h@zQ ...AZZΝc4lؐ{ү_?jժE˖-9r$W&>>Is/d̘1fϏ'OrI4IAAPb-J駟"9u͛7/-L&3k,rssoR*huQ2##'O2`UTT.]bĉRG7fooL&ӈnϟ̢EشiTP=pfbҥׯ{ ( Q| ]t!>>kkk՞RH_LLL4 mеkW9rHf͚ 4uqYlueɓ'ȚB.S|yͥRԨQÇO??ݻwUիG$No4h?3RGRF1n8y,^]#5kd_JY+L@@< ""?CѩS'7n̴iӈe!cccSrMLLdժUXZZ:t+W^^^ݻD]\\GOO[[[;Vbk o1vXꫯϗ:  PL(ˑ#G m۶ŵ2B`8::.uR/'N`ooUt: 5Ν;'!!!tԉzE6mJd'OпXzuhѢzzzRG)9<ذg####Kmʕ+9r$Ǐg˖-d2#"ɓ'yڵ#-- \ΤIhР7ӓ{&ud#K.޽{\zwwwBBBСkݝRS`jԨ{yf_UkZYY1qD~b/Rr>cz#Yt)wfÆ t֍OJKAA(Rܺu+/f899BQ抒7n$>>իWkuןɓ'ѣ1j >\$x"%Rd…ߟ!C믿RvY;;;PB8#r,--fȑ 2wwwU,LLLJeQr…xyyPԩٳgڵk1G2h puuF٩FrvY`ii׮]իL0G:upww'44T6lȈ#Xf W^:t>hUuxyyqARSS՞|lڴkײl2\\\xٳ<|;;;bcc$  ڋxzz2c }x,SE/^0k,&O,^D}K^y&wպmO066: 5'''rrrJtkFF~)gf[ J1cp-Zj>r9R( &$${{{._,u,+mEɂ>sf͚>}ԑ$?fΜ9L0X}R穅2uJΝ;L7|#uRGۋTRKEmbbb8J-SSSLLL8vXKǎ #88iӦ˺vM@@M4)ֵEVV&58p /_Fo___#Ui*JgyfvڅԑJs9*$1b6l ..;wCJE4lggg5VYXX0o<~:Ǐk׎FƩSNV>>>\xgϞDϞ= TVo]ﵦ5cmmݻt5%K͚59rnnn3WWR3XAAx=%333ׯg֭jlJ )KEK.n:,Y"ށ%ñѪ=mۆݻw: ݻKQѣk}}}"##֭+{/_^j~gr9JR%լaÆ?~ٳg3sL5E%KeffC8x  :RҬY3Ξ=K.]ݻ7-pqq! Ǐν{=z4FFF+"It%ڧ2:: ܹ3͛7ˋXcʕ+ckk… 99;v`iiɎ;FK\\;SNBCC:u*C}& ;oίJ~vl  BYʡ>dTXQ]ʸ *W,u ST|oߞQFITӧԩ1j׮] >\ BӣGΞ=Kzzظq#r)6l^ٳ33fZk( 111:ӓPΟ?O6m:[w=gՕCH\\7FPBN*u")))q°:RTreYt)1'ɰdلBPP:u*"O,^8Xr%tԉUVcZj1d֮]˽{ Ν)M6͍TOO nݺ}&4x51b'OD.Ӯ];$  %ƍYzuP&h)ǩS駟k)|޴.wr}lll6W^E.3|p{ѣ B#T*8;;3ydoN ԾbUFHHǏʊ7n1+VֶyM~ Ԕf͚1m4cZdާ$:}4Zw^ׯO"vڴhтGyyy899`V^ogذaTTm۶^TTZ jժŁ_Xd 666B 8;;:::K&RI߾}עر# p6mZ,A׮]9w+Vښc6mʴi $55F3g aѢEDFFj*]]]lllXv-<`˖-3aׯرc9zV܄h``@׮]|2,[ >,--o8wkښT5x%ŧpl۷/+V:  -Rɘ1cS˖-SW&A(RwIʢ hݺ5˗:߿`A+ٓÇ|Ջ}q&Ms?˗/gU&IEͥQf899qy233裏صk+]x1*t/7e ^{̇~Hz%ϯ\.g޼y^#""O[.'O>(<ڤQF:uҿϟ#ӳh۷ӢE ~'011Օ?)*Vȑ#9tx{{s lmm111oΝ;RT J`` O>%88;;;Сuɉsjll\qioo;6o̲epssͭt  /WQr…>};v81l\.gԪUK8Kߤv/^Ԫm\|KEԢo߾r|8GNu۫![r%6l`ӦM0++R4kѢΝc̘1 >'''v7n)))%}̌={roc%K^^E7|ܹǏӣGZjEhhx Y`<{`ҤIDEE1|pj֬)(դvL>s͸q_iҤ VVV[N{;7CCClmmYp!\ziӦq5Wcǎdgg=|l۶ԩS`Ʈ]X~=Ӫ  {۷?>޴nZHjjjxSiٲ%&L:VƢJիZU rtU(;wZjuy:v숾>gϞM6jJvN>_; $I%Q*()o㏹z*ϟ?gȐ!萚iaˣW^ŒcժU$%%hTT9^zѯ_?>Lʕ%sqqرc\xv!XZZ2{lN:%(5YfxzzCHH͛7gƌԮ]Cu-[2o<.]DLL s 0wMvv6:::̙36mD>}HKK4bE666ܿ_H  x_|A&MpssSgA(RHOOjժRGYreۧƢ;wHKKm۶RGQ`zAr夎"jO^ |s۷nݺaiiӧiР3p@$:DEEahhQʬrejԨAe}rs5ĉ >A^ԩN2 kkk]5jI&M×_~ɉ'uS៪WΔ)S8wQQQ <˗ӨQ#8pV>gɄ̒%K}6zN:^>cå>}f͚ѥKILAAм.Jfee1sLҥ&2 @/)`RSS:Vۇƍ4nܘKE-Ν;GZZvvvRG۷/*zʕ+0afb͚5ۮ8L8;wH*U$ˡQQQbtk رc_R J%A&L=?~̒%K^Y0Q(гgOtRrXnk֬|}&D:u3f ;wɓ'DDD0l0?NnݨV[j46j޼9K,!!!{R|yHÆ qww'66VQfM\\\8u7odǏg ֭[_<|||077Naٽ{7ׯI  fo\LNNfÆ |W/Fٳgh^z 3fзo_z-uUEd222b"իWȠcǎRG[.666yyy|glݺ9)J>3J%[lAWHQQQbbb"u2u֬Y޽{S\9ttt000x\v sR$//O]׮]cǎp_?͛7'5khgҮ];ϟOdd$LJ\&OLݺuҥ ˖-Ν;f--4h7l߾ϟcggGfXh)))RGggg¨X"fff舕۶mӚހ O>KIAA-K,J*8;;k2 IKKӪ$7l؀\.gٲeRGZ^ IDAT)y]7n,iu9s UTV2dᚕŧ~ʑ#G O?(7o'N~Ӫ\.L&u2^zp!RSS fҤIԩSS# ^^^;wN-(U)Td(IWYnσ|{;PFMʕ3ļm{d<- -OAz%y ʷꫯ$e25˗/se4gMNJJ {‚%KФIpssԩSbt000`Ȑ!pew75bҤI\vMѡCΜ9J"%%;v,&&&,\TcݻsQ\*AA(tTo0ӧ4nOOOfΜY enݺ%us5kƈ#:ֲ~lܸQ(jk.FIvvV3f < 88X(1IGGG._̑#G8%8::fGȪ[~VQWPTDFFȾ}vZQYPиqc_NU*)PTg1_"[g % ՋBP?-Ke;=^WG]@OWt1PNW}]uu|Ȩ6SOg+FFiMA|122bŊ_h3066:{W^D $СC8 l:::ڵwgذaZ;v]144dҤI#ES#//sJEi[{066֊$˗i۶1Aԩ'|Ž;ё/T" 3#Fh]A^o%m--c!g#C~?n_p^*!s\z<5i9}GBF^t.f(Q()P޹ T(PQ(,P,GY$dqyrC6||#Gp.SƑ4'>lrd,<'#_^%39dϟ_b?dذpBÃX [dXUVeܸqpjԨhҤ -|:իW'OҲeKuFLL /xyyk.LMMqrrƍR}gfff8q]]]:w,  H_JM61qDqWPҨZ1[LL +W[DXlԨ1"//hZn-uAиQFq !!!XZZJ\ DYnq.++xZh!uWH-~Fѩ\|IxR:Ai~_Vj(q]+y>6QŋRB M׮~^DEL s􋾞Tqv> Ĥri38=#(>t.>$:5y<-x)grժUx{{3tP233LMMƸ&''fO1666(qҒׯ^^^4lWWWn޼)u۳{n*U111_>C8{ԑ|@XXkצk׮ZAA(m(qrr*}6{B RQ;\REbVTS\dqaA8Sҹ$$f,㮰بR(W<00mRip/Ǫ5ja=NgKyJ% H#y.Wd ƙ\O^z.)9Xn&MرccmmM\\ԑޘNNN#mۆ14lkkk.]JllQK$sss|}}IJJˋ 7oR{ol۶c2|0 prr۷'OбcGlll ,]ïRF BBBhԨ]t=CAAJ-Jn޼N:aaaQy6tJ;v@~G4uJ>yZc+WP\96m*uA8LFJsN*Hܹ׳i&ͥQQQbbb"uUX}˥'KxΑg~ε,>IN>y/c?.uVJ Ʈ]x1AAA|,]333Zh'RG-qT۷裏XnRG|g25kְ`&NȒ%KgΜ!<<իӿڴi&;UV%88-ZУG\"u$AA2Eɴ4ϸq+ )흒 /GGGz)u2A__T!7Tq8/h=$322(((RJRG 66I&1uT  uXXX ɤ5 LBs<5Č<2 Ee1Qw,Jm^U(yϝ\? 3BsI&3*PdK}#GaÆ,ZH8%F%s8iMH,3.PzhQ*[(@ABfdq4!iD>^z(R+7_|Kd}&㉈ɉ :wLFpuu%00_:mۖkɓٸq# :sIL6_~e˖1ewuq-3g5ӓT R *pA:uD^J^  k{ё*UWA(Rxzz2}tQ)Fzzz(?HIIњ;wv\ dtǎ]v|gl޼Y.͙3k׮O%͢iQQQ4o\ H)Fj6'8ƵY$e[4UؚPTb냬<(RO,5ttt={6gΝtޝGIK-tuuӓh_㉌ёzQM7uIBBׯ':::`ccC@@֘1cؽ{77ofرobb/weʔ)4l7779)W]tΎ'NHIAA뽲(ӧIA2y|w}.RG)SC4b|J"66V%ϐ!C$446m9rdَ9Vo@RGģqsIQT!ư:*Elyg\xIRf 1Gxx8XYYqE#]>DGG3c \# 4`ڴi)WNNN\zpׯψ#hڴ)-*]Çٻw/#Fxmlڵ޽{x{{oaffŘ-IIAA(y)ѣGq.ofժU|w2iM[>} Eɇ.VR(899q 8PT033[n]VlɌ7C$I$Q*( *qv>dt?3y]*R4PH'񋟝O2y/ UV\p&MХK#u$i֬sʕ+ΥKҥ u),m`cc?r{{{iܸ13f ..Nxok׮:t#G0`rrr^{|ʕqss#66sZhCZXի}̙3RGAAZ,JbaaA 3 /ĀR)W_annQm| Ex5j$qAP/JɓٿўDŅCRɨQT֭+ֵ!&&&RG)Qr H&(>$e"U)ŸREbf>g3O#:5+TfM?~<];MLLpssԩSAll,4jԨkΟ?/uչsg;ƙ3g0`b߾}<~;bccC```;H__;wboo=RGAAJ-JgA(R8Ҧuo>-[qmSSR|:::ԭ[W(V_~%7o& ]PfM6mT,X@xx8v*3{r,,,dRG)Rs |Ihbs :"U3Try. iD>Yve͚50l0233U,Z~:΄ҹs(KZ18UR777bbbطo=}%PWVVVpIOOuISzuG}_T&믿ҩS'퉊:  yiQ2%%K.ѽ{#@]R5kݻw^8euJRR%$jԨAr夎"j3w\~'nJ߾}_ycǎeÆ vDxx8gŴm۶X, ViN9 YyXr?J7JJ2ΩtRrrqqرcammݻwT ڵk7`:wLƍqwwʕ+RǔLaܹs%PWm۶ѣrƅB׋/ҪU+ƏOӦM}```ݻ޽{SA4ziQ222RIǎ; / 5jԐ8ɛ۶m,YD(e6)5M<^zR駟Xf Æ 'NHRRx4FM>}6m+Ih޼1$ xγpE ŤGY9AFvLx666DDDǏ:$>Cϟύ7r Fb׮]iӆ? 5 u&L(*eeeIZn͉'}6}!##ѦMu̙3ƍY45IJ˗4m;;2) n/-J^|zQN#NICCC*T u7ƍ+S])%L&Ӛ񭹹HC-VAkZ3ft7;{{{~g Sͺuz%EVVeSRRʼn<^t s"9Q)Y(c 8q]vgϞ,_\HjժUQ2""=zb LMMiѢ-ÇRǔDa.::޽{3gLLL&--MxӼysN8ALL {~$)ܽ{ɓ'[4711QͩNŊ9x kΎ$I  h%]FV; III)U{SRiּ<)JgAx-[0m4~cOɓ'5:nϞ=l۶7r9J%LJ'y.*DgPr(U/'r2)25YR%/Օ|cI___ƆccclllXnϟ?:f333cժUܽ{-[Fƍ7o^DYf;v߹0 Pvm<==cΜ9ڵ sss&Mĝ;wԘTZ#GP\9uFrrdYAAKwܡYfŝE"ƣGXh_~Q)1Q,ҨV1쌻;o={ҲeKuK&%%¤IׯF(ɢ044D(^z.a*P jMN~ RcK~N xI%+uI0{lΝ;ѣ=:V ɰaڵ$''o>LMM1cu??9Tj׮xzznݺ.H O>dffUƜ9sgڵ8qf͚ٳgՔBw%kUAyiQ266L}#-5ͣRJ̜9S(e6oךgϨZ1᝝;w#F0~x,X2e ۶mS* gggUŋzB.caaL&:J,OPTɘZKv?iFN4s׍'MoQ&*PT\yŭg9RǑ\~'!!+++.^(uа`ܸq3aԘʕ+FLL ФI1e˖"0`}Nb$%%ѱcGlmm VCcllLhh(?O?%7ld! >QѣG4nX8Bi锼q7ndTXQ8euJKC-DQR(r9}{\5zh*T Ԕ˗¶mۨTZ]ZDEEѭs- ;A<%PotȽOoehh^^> ,VZqҥ {:RTzu9~8xxxpEt邩)~=[Ŋqss#..~7LMMquu%!!Al[&88 @Nzd 6H)_<{u{ƍĕ+WprrB,%  0(J>zJEݺu#NP5k-ZI(WԖNt*W,u AxkӴiSv܉{bŊ8;;b a }j9giEͥq)9SŸwzz FTyxۇ^&)Y䖢oԬY`ƏϠApwwE066f̙\t(ƌÖ-[hҤ VVVcr;wØ&鞇m۶%88s1l0jccC`` .]uL0&M[l}[h޽{9pӦM+5AA?/kժUaPJJJzq:Ē%K}$diVm*JjӵeӧOr:t *nnn<}[ 3f ͛7믿VC)++xTdQb+N%Ip$Ftg:/ne͚50|ޓ,h޼9B͙;w.| Vs~Gvލsٳgd;v#FhYa[ptt믿QFxzzSt–-[Xf ˖-z  x^zB%SRT2k,뇝q?Nɒ)??_kF eCvv6~)j}Mdll̨QXt)ǏZ͛\.ŋewL.T*( 3n{Lܙ`. Ut=7d%wKջ\u#cy}ڃn*K1QFOɠz;[tGz}u鑜[ʶAEȎ}:*l`wS׻!L:5݀՜{o1?Vb~ aǸt6Bx؈?lfPX 7ɨՅ!KWвRQ Y4Up[I$܌%d>M/~Od7iH:Q?橒Fe: *P03} IDATzrqqy 4kkk/Sybkkٿ?[neذaT^J۶mQL< &yf/_Δ)S믩RJСo߾L87;jLLLÃ+W|r.]ʄ 9s& 6Tʓ'O:u*FFFbr  [x鞒;AJISϏWh"!%(J B`ȑܸq4h5+n߾gΜa,]M1]!&&&RGѨ||&Tyy q"#()ntALLLppp ""BcN23gLppAA6(Jq- OAA%v|kNN~-&L(W&(Y2PZT*\]] XXXhdf͚… 999?;;;\]]՜XXX ɤQi T*i^T2Ԧen#, hz?Vv~dI McG8soD=;]^N.(3V*8ǑGyRP N4|U3~]czkJ=Ojf'U(G1Sw* @p9݆\jucd[? bwi.4H*Y{`E1Mu\g1QTj}ujР'OO>Ύ+VHT233#66>_>vvvh{Tgی=OOO,,,+}Kټy3˗/[UT 777ܹæM?.ڋR/^̈#`W(Zs-vo# kkkW_qy$%%vZ:J( PxŸ5Cʕ/C#/ԝwR8bn'79, ^#7ؿ4* Ùυj2z|3_=@F (PVrտCՎ0S}vG!: oh:5$_"{IWR%vލ7nnn5___߿/Bnn.Æ Ԕo{IQc֭[1~xڵkN=ȑ#Yb|M׮]#$$ի舥%~~~j}i׮#>>^mގRAhBӗNT,Y̙3Wq-'T B_Yv-}zt҅.]/^IITTT\PNVNP")/j?S2y/(udYw76ɢѼǿ+5XEpU( %Tdyze^}R3IܮY0{lvŶmݻ7)))R**TaaaDGG3~x6mڄ)vvv-uLhРׯԭ[.]@LLL?yd.\ȗ_~֭[eMx{dkkK`` hтӬY3|}}600`Ϟ=ԬYH ' B! %N)L&c̙RGAĉL>LtJCVQ~uCT<<Ȟϩi,D~BQM7tuu%º]A>_fu(Ugm2, U9l5 oxPRǔChkSEgCcxas* "?e7x^\tuteqԩSܹs:p-#iMI\\AAAT^ &P~}\]]r5‚ݻwiii_{̚5 &Wadtt4}<==IMM}WR@=zt^ךOAAۉPvJݻwYj~-UT: r駟uk׮|wot77odOrRY& *CrUP*\]C^S8|㾘#}tJ3݉ U:7vh9t#5t23=gzL\I e* ^\=E?N))U*T6mIݺuСG:֐dϽ{pwwѣi+++֭[GFF1֖K.b v܉gsѢE9rY*r]&M/5͍:wFسgb޼yjJ,<)) (J %NJJ *Tаlv3qD jCC˖-wTM=AEJ\NjP8 Np 8*BU@kqD[ڂ A2sXue$y9y}B1ދӧO~\PsΪ d2|tԉ(JgEk]Ǩ0?}jUH*yU/:~x|r3vހs Fa`VŎaVXC~~~nnnnիg& ݾ(ɸ8г%)֬_juH|ff&~D"Poќ^zNM08p@s(uKEEE|Ziiio|uu5\\\TpFk8y$~WDe o]ezLB:#wp  M1Z!Xf .\iӦa֭6c޽Ά+fΜѣGCKKx u ̚5 N cccUVVP"==fffJ>*++~ƍ2d.\_b֭8}4zgggalG(\4>+%)SRR_;GGv(r9Ə?#$t>}_v-^;vBd2{uk\@N0u6ӣd1 @߿wСCUrE@VVR),--1yd! 7od;tIIIDZcйsgDEE)fC 455LxGCp8xyy!##066aoo:p8سg> 6 JNN=}EQDRR;TJr|:t(v(6oތ͛7#::lǩ|r#99cL:7oSYY߫jranC>Mk$mU"nԜ1 4y07jTQ>CHRhiiw_؎^033C`` n޼'N:t@HH؎hFFF@ZZ84b+alć>txWOEkӦ ~gaa;{@("11=z4"""Ю];!##툍 T Du.]RΝ;vZ(={޽{q : ,@ǎj*Z |8z(TSYYrZJREQTA3ԎƤI`kkv(rrr0n8?ly+Wܹs8y$?~ ś=ԒL&GN؎tv xxx:c"&juHÑ#GI&!((r9Y 66puuU]&m\rZ3PYkמ:u-[ۘ9s&6l؀# w}u âEpI&~444v,nݺm~ӕ"-- 9sܹs077ĉ`|駨z\Yc5M8t10s9p@Änת $$;vƍS%)DHOOٳgѹsgL::uD"Aqq1cǎHII֭[e ? f”)Sp9 |BBBpm\%kΝѣG&0($VjjjKϟcG۶mYBQE5\UUD"*++qahii靖-[K.a׮]شi݆d2Yغ˗8wlقI&pwwGpp0^-Z{ .^cػw/ѱcGgnt7C$(z=q`B\WL)SԩS8s p#\\\pcڴiXv-Lvab0443PSS! 1rH+dLeC@@}v\p666ŋo-[Rץd֭YNBQEQT]RR+=\.g\n`VsPEQ :tlOz!ttt0|p5LkkkcKMM ^۷cڴi>\\\`$''ŋhٲ%ӧO+WzٳTgϞΝ;8}4oߎ Da3S+=RCϤ}Z[Ct~ uֵ6mT ;;;xzz"11#֛%N:ŋcҥ۷/=.Ş={`jjӧ H|CVVCÇ糘>v(ꀖZ)**lْ =Bxx8,XrPEQyfl۶ v킣#qdƍ(++CUU"##َʐ+%o>L:vvvE=0c ٳ׮]CMM \.Znݻc;w.8TR#--  H$#:7LGnan-TQA}Mx>Lu4wĹsгgOxxxȑ#lGzA,ڵk8y$|>nݺ!""mh"\pO>c,\DUC @ ːH$4iVXcnj36?4e7oDǎَAQEQTRR+EEE7ؐo6l.+(ƹ~:|||0rH,\8u333,X刎Fhhhl2 |>:uRٜ%%%C^^d2],塢q֭ t\.Wey߄`σ6 ˪eUR.a o[ Ra9y"LS nܤxa zPdfkk/m۶pvv?v$ 8D"^Gpuu@ @bb"o HpaoX\.w&FJe|W_}޽{cС*(jׯcĉَS/ .͛DDD GQT]w޿xΆ\.ǧ~ ţ/lll`aa.]v}`JH ݻzX,VX>} b;̄ZEQEzJT_~Arr2N:EWPE51ϟ?ǨQн{wDFF^233k.@GG筏իƌTR}T2'<ߖ/_+W")) lǩGbȑ_Ce˰~zdgg72 2 O<j{vڽxϯ\Cv x1?DEN0p@ 8Ҟ;^xu!44666شiSv jrT 򧲆T_eYM۴L 9Ց@~>=9A6?k%h Z <8 M. ;?JARr2zY}!66C_vXO~H^^,--WWWPT\4^"pS=~ III*oooG)_szDD$C!jfN8A\.ٲe Qꭢtڕ3^ו]Vqq1J$..HDhjj " X,&$$$\R]];v$9 #p\I<==ɦM2xrDGGSSSbbbB}_ >H$uz|UUa>|s.Kz_YA֬Yt:< o[?!-[$D)CLL "/_d; E5LsGQ#ZJRjC&ի*o׮]咬,G)_sz$ENĉَ ׯ'|>ܼyמ8q ǏW|0xLMM%$00xӫ-jGIHnn.QJjJ͛WED$hK5ŋ xD,"OZnMJKKo#F޽{Eeee$88hii={S_7o$XYYC$00dggd~Jr9 & Ð`,&Ɉ/rN-Ɋ 2vXIv]׮]Kx<矕]˗/'ZZZ7CD1 &?VaJ͘18;;r9-%)BUEE ӦMS\4/ZJRԛ{{{$cbbbB.\1FItB}mUU%III*uttj ?>OH$"+UuSٳgd߾}ۛ‚ Wي;~߿Ob1p8o߾ʕ+*p-E:_GTZΜ9Cg:Er I!ѣG ]F9@Ipp0SYJyoNx<4i`;@>|8a[e?K^;rHҺukdIMM W^}H$Ąr=%eWϞ=ܹsَAQMJs?ӜQسgxJ[eW6m"|>+}.Ju-%)&OLInn.Qď<{c===jժ7~#ϯ-G2l0 #BDDD\k.i!*++Ixx8100 m۶%Ѭe4iرc_ {oIB#Z"$::Z+jjjHjj*'-[$pRXX8q~C6R < VDuA83gNObccIdd$'BXXX.155Un.h2tPI\nmQPP.Xٓ$%%kkkMY˒Cx<^ h{%\.A"ɒO\. 2Q5Dyy9IHH DWWp\" Itt4T]roߞtܸ҅q8T#p/>ۑj>|hkk#F:_'JfnJ֯_Ox<^V񗗗HҮ];A|}}y afJܟiN(}ELlܸ/^@``(իGPPFv Aǎ1eʔ:=yyyHLLDXX>SxzzK.Eyy9|}}tT*ӧOQ\\ T8H$b)پǏ///O?l޼<@ZZ`jjd2lll؈LoooxzzYYYH$c-W_}nݺaرӧz)ЦMlٲjkk#!!NNNT*!"".\@YYz쉀6x҂bbbPTT}cڴihժJe000 ؎D5B~ J~s ³g؎#Gę3g0`:GGG\/FFFSWWWL:ίZZZŎ;ptRrbIIIJ!QEQXFAA,)) 0|jJsQEQsx{{O>X|9qƍطoBBBj?\t W񨭭 KKK 6 aaadX,aS[<@$ѱA2=y;wDajj3f@KK 111(,,Dbb"b1>!`mmԛ"$$z*~'$&&cǎJիלOCKKx<̙3;v'O}"Ipppseڵ =z?ب1B[["(((mPRRÇ ~~~HNN!D٨177? WWWB߿HT#0 H^DDDYTܾ}}Çtݗ_~Aa„ o2i8vڅ\Z^jhhYYYGnn.lmm克/*)$%%A(($6TQJ?W<EQbB0yd`Ϟ=+̜9[F~~ߊGccc899aܸqB^^^mxHRiiiD`` D"O̞=eeel?7*//ǡC0j(a֬Y022QTTcƌUVV|REap8رcGWt1p 777 2^^^oԸ ebb???$%%FVV<==Ѿ}{xjNOOGX,Ƹqv$455\L:Yecc4|s^0 [|ӧOWAJԩVZիW7Lp8T*ѣGQXX>}@ ԩSJH|Gvv6v(Pv)lݺ-R#EQԛmذGE\\َV%%%Z(`ll ؾ}ߊǸ8HR<HJJ[λ~zC"پ\.~m۶Faa!p:tG~z,rZJpwwǘ1cၜΜ9H$0 S`Ɉ@UUUPt1 [FLL ?,a˖-ڵkHR\v SNg-v\.6mƍb L6011D"իWamm oooB\zL۷Gjj*455??iժvڅCa׮]Bgφ@ ĉQ^^ޠ1Ο?cǎӊt?#|>>#PEQFAAΎRe˖ظ*(J;w .ĪU_ǐx{{ 011&LPAAAprr-Z<e277ʕ+kWI&!((m۶;1|ܽ{lٲeԩSSӧ?(( B߾}t}cKI@AA:1QLq5̞=̓+\%T*P(ĶmеkW ,, lǤ! ¾}Ym>ҥ :4<7o?JSSS9spwwǵk/ٳquT wŊ+=ސ!CӧOa}VY7QEQh)Ij}0qDX)((@DDakk999d D֭2WVVu.wFTTcΝ8wv9rΟ?_Jdǎ1b_Q(ƪUp5xBo,GGGDDD޽{HMM@ իѦMDEEٳglǤ4b={m L)Ν;owbbbX9'O%<<<}Uн{w~F"uaaaXf]㏑tHLLTPVVSNalG((h)IBrK.EN秔)(ő󃖖TWW#//*uuuabbwwwސ X~LjbBXXXV_}\]]zs첳y^ѣG1|pk˖-;.\[nA"Jd2u>}3gƍ)`ɒ%Dppph8(%`JHMMm8*&k׮HII-z'N(lrÇ ̝;Zbbb,=Ν;ggg\tH0 ???\~"'OF߾}!T'O# w>^CCvZެ>3bȌ hÇG^pAVw9q1dPEQ@Mc IDATmQJ^rqqqXrڜ{DQEݺu됒ݻwȨWUUxׇ%<==TMP(ƍVjAHHHQT)ٻwo`ƍKA&A `РA翪>_[B޻wQQQS~~~HLLlV6m_~A^၄#Q dll\p/_D^XYaa_nݺ!44+Wą TRAdd$i&Dҥ |||Ԯ<|0\\\v(TÁ>LQJu1<{Lc9ّM%H$b;B `;D\phjjuֽq$77$%%HOB! \. 1qtt$"HJJJJ\\\\>kUUUxxx\1H\\ aҦMHrss2~]CU:mmmҵkWr1#IEE ӦMkX...dܹ HE !B+++#B/*dH WݻGÉahтb?~Ȍ3%lǡDGG-Z6mڐhg | #|\.'$ݺu#eee*J!!!DGG)uL"0G$..RGlZjtG·~Kttt>K0ѣ R?tԉ ݻXao:Q{d*ʕ+חp8bggGYp޽㑂MQEs?ӜQnJnZ^^^ (j'OҥK8x >c;w6,--1l0A&bT*Q\\ T8@$=P 2D[= "??^VTT 66...pttą fEHH؎V'Xz5fΜv5zUĶmO?W|vvvAff&z)S111Qٳ055UٜEQE)-%)C Jq1XQEQWRRR[ɓ1gZJRj@7/^ ggg L-[Za㪪x@NN ???<~X))é-!ܹxXXX`077'bbb*>%rvvFFF*++_Hhhh 00iiiwU8x 1`\xݺu+*++\vZr,ZHԖ=zɓѣGA.+eh`ȑJ((ա$Rٳ8q/_1)w-CBB '''&L-mll˗ERRRm8dwXd OѲqq,\...1c>s~ׯ ǏcχD"Q6 `mmv&_~McŬmXr;wB{! -%C||BUU1 8z(N>]{#)҂bbbPXX}cڴi055ǎc;$}˗1c ̜9ݻw>&:@ҥKo|\V۷#99YTׯǶmpYocc}ڵkѣƎ={ȑ# /w܉ &@WWWREQzMN(z%KP1)%%%HKKCTTjG}}())֮]^^^x֛fÇ,H$ڵ+vH$bu`aae˖_~DJJ >x!pw-sf$&&"11[G6[naӦM7hѾ}{$&&"447n܀L&+!>sAѰ P(Xd :uÇcػwIFGG!!!:kS_II EzzS; %Ko>ܽ{=zŋ4hHMMlx{{M6ڿ;I$[_}LpdѧO8q}&NÇרڠضm&M,_r**XW\ҥKyY[ >u-[T?zݻ(RaaxEyf 7mۦx 0`NbS:/aڼy3aR eeeQjj*d2 #___rss#TJڒRXXd2JMM,x Pe(ԲeK;JKKwyPƍ̌Ν;wXʺuH*J;:!99^zIeee|3$Hi'vt֍ƌ*諒Ɔ;~h233^zљ3g ү!dffRdd$988ԩEFFŋ$$$$D">ѳ#5jԈWRRBԲeKvZǬZBΗAVVVw(ZN"___8C/۷I"ڵku üLe|ƔU4~W5kR+%SSS~DDDt[ 0LPX ooooh߾=; K,AZZr9򐑑$XOh޼9[e(yyygA"B@bb"\]]ѷo_r$''ƍprrBPPP\t9899Dy_}t CϟGTTR)ߡ̬Yжm[]}*ܼy3n߾ b\3g=z@xx8***޷9;;#** /_FFF!бcG 66w;:+ ;wĮ]п>>Zb^z?`ggooZWh_~ ܩ>aÆ5kߡhuIII8|05jCsl2>c 0 KJ2eR2""믿m1 𭢢HIIAll,BBBx͛^CHH ҿ!BxzzB&!77[n=Kl>>>HLLDII ߡ...8rЧO?~= @FFT*ݱm6ոqcoc#GGGDFFbΜ9|04???'OF;?Ajj* Hпx{{?q億b„ aa}bIIwwޅKNzatC.?1hkk www5 &7oތ //d & {Y=z4|ML 裏ѥRܸq xxx 00Æ ŋj%nӱc̛7}}'%`ʔ)(,,ƍ_Ih׮كM6_E.]MT*&!srr D-* /͚5Áн{wؾ};!1zֱcG>|~~~1bf͚ZBjj*^#FCǎM,wzi$$$Upݽ{7ЧO 6 J " vvvk׮ŭ[ܹsh4,)ȝ;w޽{C*ĉdhԨߡ__KHJ6nXx1H}___={=z<`(&!޽#;;Æ CfteHNNƨQ0l0Z=JGBB\ݻw###|T*g"˗/޽{egggL0;8p 9={Ν;ѣp%ϸ~:f̘w 0 n7daݻ/$"DEEaĈٳ#cI.#;;Ĺs琝˗/ HвeK>>>pvv ڴic+e˖-ذauHOOӧ?ꭏG\\D"BCC1}/h۶mwaxwo!b̄T*EvWJ+V@DDvZAرáCև!' G~WUbrСFjj*guM6/L0...X|9Fi aÆFpp0_M6aݺuG֭1|p;ݻw;T# мysL8ϟG\\IM`bgggݻ7mۆ]꼟]b8p vZ 4hQn}oDGGcӦM={6-Zw8`֭ꫯ+++9|0 0 ^6)믿ԩSaT ür9pVVVX~=r9<<<`"++ eeex񁽽+y&BBB0uT;|S+WD2=̜9m۶Ŋ+_ڵkW *1 _t9899;w^t _|vUIH+ 6 ֏=z􀇇,YWL2331tP iaaa8<Ξ=q!%%=z􀋋 wF8DEEaڵXr%ƎJpqz^͛a|5~pB(J̞=[/}gҥKqYy.aĈ8s L\["$$Do+iaKJ2{Fٳ秗Y ØJlݻģ4h}"44)))*g.Z%K ///C `j5>Ch|ST*JMM%$PH^^^@|gN>M-[Ν;Ӎ71EP(?J^?>qGjӧyzzFK|HOO'(%%P[||Sb"##I"''կ_剂ET\\B;y$|M6a%%1,)ƍk|q\Zuu c\LUyyy%fW}Rⱬ@qq1988РAL6_RfHP<{J%QuO?jI&QҥKJi{Æ {{>+I$6mQZW_}E666K*r@鯺blIʽܜbbbcuVW 8Ď3fh)btO.S^UVz9'\r4iB ڶm&eJYYnݚ&Lw(5h4۷/k:И~fbIIa c@-Z@-zLDDӼv$IǪT*~7{(**i *HOOG||<wwwXYYApww?֯_\,XBYY Lpss=B!ϟѧݻwĊ+дiSыT`̘1<6''߿?4iSNaɒ%4@/ѣ7VW*ذa4^ܹ3 ND;w^cЇRDEEGۑ{{{C3:ś%Nj> 7XU GGGX}W177Grr2퍌 b"cǎnnnQL&ý{uVc֬Yhٲ%|||CչaÆoƊ+pBP(Č3x%޽M6śo7n#%%Gń チbz1Th;uСCXt)8{cHNNF2D0 0N3Kf06f̘'+ٴiSqI"5mڔƌw(z5j(x1 ʊڷoOIIIh4GB ZٳO]%9}t"T*}7<&''S۶m‚"##@=׮]#333ZbA=p%fh/&KKK*Ƹb|IB D7o;$O ;H$"sss*;^\|qƵ,3_Խ{wrppy{ǎ$(**233I$QBBZݻСC69MRRRor,,,hɒ%_&&>L;vL&;:$Ã8 Ppp0̾۶m#@uL(3ۗRǎK.8֮]KԲeK*--y?|ٵk|BԼysO̹}6P($ھ}^ccf*340"V1}( Wc߿|rb1:tÇYfzaB@vv6݋x۷9ڷooooc޽*,YiiiCFF`xyy ٶm `ccw8z/@R{g9999r$ '''?QQQJ|~rq8nKԦM e޽ԣGK׮]%ჩϘüXRСCk :Zz%//֒ݺu3F7K.SFF%%%iM4! lmmÃ)&&Fx,**#<2-ZtV";wP(~Mk׮I Ppp.>Kii)@ uW,Ӵiüyj#-(11Q}>LJL:99D"3fdرcq%''yׯ'&KJJhڴiqZ46m5nܘEEEz`G:uz$P(y5?;v PH-y4`jڴ)җ_~>4rHruueBP`` 5jԈ]vT^= cp+Tg0XRŢEFbk=ĉ Ӈ 1S+//cՊwuu& oSeuV8vw(ztRRh4$ʊh|S'O$WWZnԨ^r?S֭С]p0P^x.,,x黺-[@ /DDwVZQ&Mx[c*T*?YYYP($d/`RӦMI*ҭ[t_Ξ=Ki{!h˖-I(&&lllI&rJR*|0zc*3<0̳=%^۷ӧOWjDӧRSSaeeEr?~[lAll,BBB [[[4h=z4㑝 {{{#)) (**BVVRSS''' 1L <@HH0x`ѻ-[w܁&OI&ٳxOu'N`ܹ5|:;wZV#..x뭷pT~T*q%dgg]ݶ~ELL 8%e<-Z`ʕ|e{LV8p Μ9aÆa\.;:I( ͛cԩhܸ1|||e(]v[n8{^b533èQp?ӧO%ARhoƌ3f q ]~}$''*'cΜ9:mO...x_z>^D"ɓڵ61(e۶m[aܸq8uTT$0qDTTT0/<<FyX"!!!Xz5JJJG.$&ׯL۷pqqN377p]d2~&M )))%2.\3grrrгgO^3 ʕ+H$cJ%VZs5?+WDVj۶6A,ϧhdggcƍz@e˖齿ՠA̙3С #F70  dPJU(5(PeZ_ʔ)QxݫР{OM=,WRU{y!ik2Hl HOOG||<õG+++mׯ\.,X'&}||`ooPc_~W ҥK?#pqaѣXp!̠T*i&h4O!T<_OxPqϭ\ǎN8_;U4PTꫯԄ$h4ܼy"~w=zs5cIJ@pp0 28"Bb~m:u ɓ*[Wjׯ_GTT1tPmHOOaÆ Jr!<<jZqZYY!88Ν1|pD"m<ٳTAz$P=3oN_J4Pj嘛cÆ W_}{-[@`ȑ:o/GDDΓ;wbӦMhذ^zHNN޽{N:!44|0 0&#IIIxo3uTeQţDTJ׊G}*@LBjAumXܼr 3?@$zQ@, p 9G_?8H< cty_* 7n@vvcϣ@e){{{޾ƫ]vz-5Luqؼy31uۃÇkWN*"²e0}t`5ʋ*BJW058G{ѹPѠBEPJ\5MPyMW}'r9k"00'ǏիWc…t>_:`ٲe?~^8r^uO?KRf̚5 bQQQ d:TgXa6cTBr@Jʇʯʇ+f\r*[𢏤㳜>tѾs'yB7!B< W@ϥTX9)g.4 `&6~R*yfLτc_XR1V~~~8z(Μ9c҉{!00;wY¾}?=jMѣGw=uX,FfUPԠDڥҠB]* ʔ*s7'FIanM@ cχ馑w@s?E\e\$HP '":wʼn'piXYYm"q!Keĉرc.]<;wFJJ o{HݨVnڵæMVu)1IDXt)>s h֬aӧcŊO-w- __j(QQPXAJ"ծc@JCpOt{UԳ|U Lzl*wR&X `%B*4???]cǢQF8qZ|$ ''?>LvÇɓ'qI.<.\O>w{g M6|0/TgXa605h=@i%J5TH8rH60H@Uu$l IZ+L{[(5mJ X=~81`^۷sXx5frPڒ&a SNرcM6!YVVp|x3gW^u.!Y@^Q9xP!B uRP H|JH{BEaC̝nh.]ЩK7trl!l̈́03A\CYjrrrw(kO*'OF`` nݺeU{L:F8QF[nXnz-C3)?3UTPT̙31o޼Z V+#.T+qPX&TR#'c}cǎطo_OHv܉L>s=3 0a+%@!4(zUҠBRZ|Tu_ Oj>ui$X[[㧟~1yd:t6l#ߡy԰Z޽{ _v;=] SPRD[Jv"IŰIʥK2 &Mi}̙nݺGLL ahM4ĉpBL4Veeew߅NH" 44ƸqvZšk׮|0 0F%%j+(|TD*_jٷB[av*/A D "D[3%"ؚ a)݌7n`Μ9Xf H[ŋFmLdff"?? ѪUZ...J:L üR?C 1DPVViӦԩSpBbwލƍ͍('B2%rK(T|8Zjכu!0n8 &Mٳ<<0  6Jjͣ$ UȫPj-!S}?QA^FB" ̄h`&Bw-Z@eZ]۷oCTB,Haq}۷Pt… CNNۏsA@.RSbTpW *\/T(@3 1דսo! '\.[) Tr:˖-3$P7;vÇ8r֭[0lٲ-2k80 xyV~ ˕xPB Nl#ÿ*2%@(b`'C$pBAhWEFV7oDyy9ppp7&L-ZHy;v K,UТE љ 6`޽;vء-OOn*K("U", YwTU5VT J֖fB%^ܹ˗cܹ;-c^) SLAaI433w}7|8v~Wnݚ$"^J-SB!gMO{bY\)BVX粵5f̘yaʔ):]/JUV!++ /^СC_MEc̙Gbb"^ӦMCzz:kSqƍ#F 22wx 0 c4ƆTTἼ w{n%]&$ccFv1PPjA9c- ++W?!66mڴEPQQԄdwww\iiispIlٲ~-Я_?lْ%$T*o߾ ;PTO1fL0{ɵkא>},>y ' V>.P83jC.]|rCy.UIwwwx{{###/'!)J5˰f+ԏ.\ܢ1mUϤj ˱/rpJofMz!..NmO8߱eb9:PZj|P>eŐdذaz ,Y eee޽;BCCQ\\wh 0 c^*9vn"dk51 Uiu +Wr0v,4k(--} X ;hڴg(|x"V^m >|z +WĚ5kh"DO.qb'[ā")BNIe4v?b[% )">{rP]uU]| |XZZ>\'bƍwߡ<С9_ Bll,!5pT"BdC\ y֛WB}LB3˴ꧾO,,,0uTZ :mYf1bVZ b֭8}4N~ /7iӦ:~Ν9s&ϟÇ9G8|0-[t a1 0 L>)!n'`׍_2Uٛ<0hpcc?֫A @*>5q\bۥK0w\DGG}|N< www\xiii;v3st[\˔H)_Q$8P_q MoYжm[crіnsss]P[]MLZYYa˖-3gf͚ѣG|ϟ ֯_իWcݺu:P`޼yh>ĉøqg(ºC  88}tq;'bĦV/hҚVZZҢ]سVF"Ȟw77Fuoν7x׸wr~ի4mڔ;Ҷm[d2L&-Jktğwc80𤴌cdQ!_}c?X{.dwKhۣ78^ iii\vM2, 0r1x`ښ5k_>eʔ(NfǩSY2%htyϱbfy&50qG#9 Dm5pk׮eҤI166OOOc3ݺucɒ% [k-L ȑ#ٶm;wAܺuKX^Bc98$NnɲMd wqiyrˋ;ߛ͛SdI~'}]N9sɟ/3k׮aÆW>˴jՊgkK|||Xr%֭[TTW&da  7{ ihEÍ\ɞ'EJQ6*=_ƍ)R9q*u֑H=v/<ӢE ?2&2˲FV3x`\Bjhٲ%;v$22Rh2L& /J>Nr8>JωrQ88bodQI&I%Ǟ@m߾3eVLL{)T?QrĚ š5kXd $%%Il50FnG%--u&u$Kqa"׭l+/1ʢi;+oVdLkBȑ#mCSTr>/˗/GRѹsg,2}ܹk׮w^J*%u$UjU=ʸq !00ӧOKK&d2T.e'6S2';S^R I'I^1)لw2qDKʗ//ul۰am۶eǎ7nPl\q6*ioL20o%r"3We2Y4h >\(6|0`VBVMQhѢ9naǩZwdYRrȣ Qɖ}?͛77nQ2hHLL[޽{vQr l޼nݺ{`#ʭu:_ aaDnƥhɰeʔ1KQJ*\xWV-Oθq 5Zdd$͚5#22 &m6r6 IDAT/u|L2ݻ ҢL&dF%u:8 J&-GwqrϟڟMEDν5䧬g9dG*Anp@_uuIs)숨ⱞK9y}H'>?7dڰa[ne88X׿iӦ1j([Fiv=zF99ħ=|Ob1De=r<Ӿ>k#R1OGjy>~/`xy9oJ)))|7ӇҥKgoMɒ%Yd 3gΔ:N ڵE(u,ST,\ ٳQ(f/`V݌M9d/<{P}~JVoQ;(K돊p;.L֤w1Y{Ϫ\k  t֍gcp]P(:toooϟ2e Aӧ-[O>4hЀO>3g"ug3s LN}8[s 30xVK32$w04߉;}Z.Y7KۄA? #RiCJEUi`Kzy_վkz~o1$fbA,Z[FPůY9d7M|y3}V[<"ѿ}HH>v0!<&= >?d|J\CY\n 3B,@z(+G^&%e2+СC޽;͚5:NL:ѣG3{llҶ#v؁ X@hh(2zh:*U]v~zOhh(K,!((Hh2L&eULNZ\P(~ k% >UvoPe sx+mQ"wr8,FnIj{7(Z /3O/|Nsm/nݺ|$''?^_ml-E=KGO: ]AB33tLg%(g1hg.䂆UXϴANam_wͣjk]m55TC^bǂ-D?ӏѰ vzyXī@V4'EK2 tP|vvNla֦-%j|hA.\ɓ'?լƝ0aSLaѢE|f}\S +-*ݓdz ^vb 씖oŅAI%[qwwڙM4RJ,^X(&e "Ep!h޼9+W:kyګ,lp>veFUE[}E<;V(po-CYX7fBaK^\\f6J UVΘ1cL%;̙Cٸqk-VL6M^d ,ʕ+ٶm{J*ڵKX2 e,JZׯb/?,?^R= y>^>#j Jry5fyʷD'aܾt+6qٞ T!ho_ܻuCwf}T-^ {߯pdrg*ޠ)֟=nOl9SgܢLj4rX=c=+7>a~؎Bgf}ƆINDGi_L"e=,kv(l.%JDDDW_1l0U&u, a|'f$rFQg;Y^{1~BZm3?g?'AR]?Kܹsŋ3v\k111VuÚ5k:IBa5k0zhz3L4**iNK׮̮1Ϭ/:}gMp|LKN4bu@q_s++LiPxqΝ˜9sؾ}Y2NcРA 2cDzt,A΅ yw|2M4uۗc,-?,?RZw(QdP<ٯʝyg@]M }"/ŻuTwgDG ]WpV.9t峙RUC hxx(=#R%PmKPDj*枵NM^(jnRGyν{XbC5v}:˖-F,YRh2 ܹs0`W޽{O|V?'G~ ֭SN CFǡh&M(m S~ae4bY>%P^M.&F=J>E:NԮ]ƍ3}tZ%6lQ]vѪU+lB۶mu70z<{nݺ͛sdnĥXؖxY$ߓX,(ဿ[.]vʕ+b t"ul߿?7{:J|'=z/Zّ͖Lv8uJ)GCpp0˗gx{{K?N?J$<1:[ _lTmo ;_fM6mIرcovYKթV6m2yӦM-kR,]|J*qE*U$u\9|0 6?,?[\Jq^f{uIT{56a~p)dgIx/~RRIr/..777c;R8p :t޽;ӂ$@bbI,ማZd6ITs 0zh#}+~˗9|Qrh޼9G!**@N<)uV g"eS9$x"5j0iFw5[I0VZŶmXdYFرc9wޡ\r̟? eˎ;Xl' KK&1[:SR޾U&nVUCaIQ7| |lfO 2>vy [nt*g]ۍIA *p"b+<==8IRQ͍8ckf̜9S(x"۷g޼yyIII&E!@8(&r,O$GQL :u6mb(Vםl(YF YpQV 8q+ҨQ#.]*u(>8X/쒯]6͞fh47nlN>Ms 6dȑ 2W8իy7Tն 0`VX'OLRfJݻw… xxxPN̙c;Fbb"`EIviiyL&3 EP+킃Ba "E\욷IMLzm ګi놟!"cQ2&&F$1LݝXc[|=sZFO;4 *iW*h늫JT{KMa씖N5jjբM6RGɱk׿~7>|(udرcǏ_~tޝdceW 4*Ja<<(7k #;<T V)WR # _?~xTB.]L:Юj5j]vk׮l߾$}͞={VY| R̥TR߿#G2tPz-aˍL.%{ H']カWT*~F!))fY >*u׊m۶*TM6IvJB3ݶJ |\vR4i=HE\WZmسgSL{oh~b &A`ȑl޼[ҰaCܹ#u *@-o=k-P`Zٞgƍ[fi;.. .PvRXr%׮]cҤI&NƍY`k׮eG}ļyLz,3=JEHHƍT^m۶IK"""(T&JI޾U&n1 ʸ;мeQ+ .L}Aji놗i0'ApwwdDD(RpaRȗ=իLrNzvJTT[ntbJV5iJ0[A`W&5 Aӑ@o5j[iӦRG[Z)HnXx1zm[i+I6mp RSS dϞ=RGzNYT"ecxo8Ѹ+7P~9^JΝΝ;tl2W+Wɓ'3ydr֡C $**Gұc\2 ޽{lٲmLnݺDv3پH|||aƢdjjId2YNX}Q^ӑܩZ U]<[&+M ;Ǖ&E(bg瞞V}g+7 &22R^g4k֌{O8_ƍ)QYQÐ[w2H7l&nqwƍ9~VoHVΔ4ׯof׮]RG1;[*L-[GoҺukf̘aQg}tPѤ 8Rȓjd%VǕ/gvZJ(A:u۩W u[A+۸.Yf͚ȉ'\rsLҥyw3gYڗ;WfժUXKK"""lfdR(bOӢnqU %e0{pR))aObr6Լe0cQVVJ.\X^))~3g0{l֢E;w.+VnݺR1[QE+U 8ase$ `TP |\qV[mNcرSfMJrr26UI&,ZH(y– oL41cЪU+åA͞fE(jB^/[Qj'u˓gjj*˖-GfȦML6PP|rnݺɓ?dL0͛7z7x`8ٳg<2ҥ Ν~g#̖`dʲF;LꅜiUƒ@o|P߀)Y~d,D:(9͋QGm:(Z(1r%22b>>>X$::AQJÇ7n:u:`$JT̍Wkv  8T̝.ҜW"u\39 IDAT^RQO>?9RaRF#Gs*U_:s *pE1wʺ;&ʫeYdi:ryqwJgݺu<~}kגLnLoĉ4iRq=}4sNF'5oޜU2w\?̴J,:t(=z{$&&JKf&TTT8;;+uLQȄB'5^δ.N3ŜW(2^ ٢g֮j%~n4,JTtN)Y6sݻw%ҔJ*۷ _Q^)22N:ѺukƎ+u vvvyv.Bs3T鈣RyJߕ1cJAEOGfoz1azI kZСORG3T ٳtޝ>ݻ[ܶdvJ(N%ϧǦ.ًU x9[؅E(lsShQd:t耗IϨ_>1敏E9sP~}|}}9s AAA&͒fߵ(?RTi&oN`` Ν: _)J^^^))Y|Q|Ruz!gZpy1w*x:Q^QS2k%ÇiӆaÆ1j(BCC)Vɳdk׮t<nimۖ3gPP!ի'6&..ʕ+'u.L#ߏw9SDžJxP e<(`ʸV Y&AxzBNFx9Sٚ5+-JxxUZQޞEE<2dTBϞ=JÆ lذwwwaѢE}_#{ŋo> B~ѣIIIRǒիWERR&^RJAQ# v qB'|{f%,=.U͋Ӫ;^n̷3-F!**J(9fkEI0t墤o2gZ}y?Preg**)9pi17Qge9(qsV*3d͚5T\~ A-:u*aaaϞ=DF)uGGG\ [.?Q-&}||?4i~-AAAؔi]ƒz>t>%(>0S(v*=iZԍcBnɓ'7nY?r{1Q9uԡW^h4RRRNٳg3  o߾e([hy|(J 'ϗWs9vѢE )Lpif͚Yv-?l6kJ#qi:hN$UGN>W@/%>vatPS^E!GU n޼yܹs;w+WrIhԨϧF={"W<ߟ6mp5ʗ//uY./^0b:vȠA1cj4grիVsϐU6;P&urQT * ?E5:5:$tY`6ة6x|gh윻8a&P((]4ׯ_:J\t bR*UDFFRpa؜{1}tBBB$[Q/sqI2@,J!0(ՋN6Z}^T Z}F8c(|_Cxf VV/ՃFG+kK7BՊo)}ŁUWtKKFEC#(°zC0 U=A˲psqAxB@2;ʕ+͛7)[q޽{DFF uU+V:MY`׮]~:KZ[ZMAxܻwOyN!Z |[.tqRTd(aVѥ?F' "{>80ȉ; lOW(ӯ A@)B@PRaN)`䢉?ٳٷok6OOzmٽ{7+SNŅufe2[~zQqӦMfiBwi#""Į] mڴo߾ڵXxq1>>LFb^!3SNŋK.-^xQ8(V8i$cǏE@ :L&&P&Dj8p7o䭷">>^H2 8::RlYΝ;'ul9uʕ](fQvm G:M0ajѣGK奆RdRGɶ%Jp]cHNӥK_Ύ;֭b\c6J%?3Ŋm۶&mѧO֭[ǣG8u&::O>oooپ};Jlbbbl޽{s)Μ9#u`\1H-8uԑ" Ç?i&ʗ/Ϝ9sRG޽{ۗeΝ; UV&kW׳rJ*Uľ}Xb[n9g&99qƙ,)ݛ_~'OHEfF:u ___ׯΝ;${Fxx8߷ɕ888dl2SB˭[hݺ5qqqRGIZjVWT٬#qr֪U+ܸqu2{=\={"S={ę3gXPzgFÆ w߱򉼼}ХKݻŸKJJƍcϞ=ٳgѼy +qFܾ}[!?O28}4(xzz"<<;vsbetq׏u ]6?)qJ%J OFff&/LV1:tP(TUOFF`jj:LDOKvvvpttdnի|r^_>tuuq=Qի>|8ƍsuo}}}^ptt1o<ܹSf(0ܼy'' ?iWWCCC(**b,cQ̔>4hXGQ8U0)D/X|9yf>oS:> )SHu{o߾ppp}YΜ9L2HHHTN^n駟/௿ʚҲeKtؑU1Cӧq-t iii#UI=BrrTg*^8ċ\רQ@ IDAT#?YYYǫWXG@__7ŋYG)3gΎu xѣ(]vի2SQ&L D"8"иq*wR2==NNN֭6m$eyR#HӧcĉpLR+ѪU~XLڵ 666صk1RъoPSy1qD)߇"jIֆ߿G:Ǖ۷o1x`a֭R]ÇDŽ uurc;vvv044ĵkװn:֟?>jժ3gJ}튚8q"ۇ/_Q@N:)x"::zzzܹ3(2cbb‹xQqdҩS'(Jbҷn|7/ǣm۶|b͚5u!*NjMV߿ǐ!C`hhp&QիWo߾4hd BJJ |||x###"00?FJJ V\5j`ٲeh֬?ZZ mmmhhh0!ohذ/WU0 |+W@OO=zÑ:}QII F/_"22:::RYpuu7n:k\qƸ|2-ZH%hiiaÆ ػw/9"}cĈ*z?DVЭ[7;vu*㰷(2cjjt~}㔌_52_>N}0bxzz*kF@Q8tuuq! 4Ʈ]XG qJݺG^^={: q߀%*A8s ^~ ;;;Uu޽í[XGOO5ԩ:\ڢv8p(JŋX|9rV:ԴmUŋo>ݻrȫ( |h]xtN֭[W^̣.]: ǀ:v؁GVm+s!77%4hL/JrU9Ξ=|O]vhݺ5XGիxbxzzuJ+<<m۶9(2ejj uuu^8%Ë\dff8[nx!H BOfq= :u>|8pMQ^bb"xb̜̑9vvv2d8Re``KKK\ruy&ƌ)S`ԩG͚5ݻw^,_\{Dfz}9b1~Gdggcƌ033<7oTxϬ*[>TEJJ ( &sAZZΝŋ˗/IN.\7779-z;wDӦMX,Xg߹snnn._4nܸ*jΜ9IE2aٳG*Ď;i&7N(b+""ܹ3(rթS'#,,u7{l4i|b۶mz*XGˬcīW₶mbլO,:::bŊ_%׽Yy֬YoooIטի>|8j֬Y:庻*vضm( &?Յ1rHQFX~}m}˗ѯ_?AAA\|+VPob:u*Zh:Lk/_tߣEEE6lJJJo>:}}}H$}3gb„ pww,Y]]]L>u/.m*]v;wDn`ddT555Lfgg+L!UUU7dB]6ѣGcΜ9Ě5k:WIݾ}GNk.kl޼ָsN}ŋ#??Lv퐕U~6m^HԪUuR _mڴAK&!-- X`Y)3ccc"00HIIҥK|hSנAz:U$}^ff&?:R2220dxyy V۷oY*dؿ?444ʵN||<:uӧĝ;wivzz:<==aeecǎay&!ʕI^֬YdRjҤIr _: ڷoXܹsx5HJUuG$)^8|cĉhٲ%xٳ'N::'ۇիG0ѦM4mڔPܸqC!ܥ`ڵXd j֬:̴m8<(RaaΝ w•uQRMM (..ȑ#Tvژ0a(y^X,H$ݻw1bԪUkll, ?ݻ#$$u [.֭[d :>>>_>͛L8%;;;ԫWG-\T9:u&]_ʹ4L:8x V\$Drؔ7KKKxzzb sUѤIlݺuNAiΝCjj*z/^N8l 6uiРQTT: qeċtttpat}Ņ XG⤤_~8wݽ~ >> E1vXܿu-- Ftt4~w$&&bԩ>󡥥 Rjؾ};޿: q9gϞxHJ+$$]v(rӠAH$q\Yѿg~㪌rvv&eW^ ۷uR B:x (L%%%?XhRFF(8{,Ǐ">>>T^=1*ٳgdjjJ D:gdK6o:T9;;S{@VRRR(00  ruu@z rK~zQޞʕ+( &MP($'''?YD||<y[TTDdllLGe~|jj*͘1444~MWd$ ˬчϥ: `˚/Jr ؼy3L#G"88u$Zl 333:tulݺm۶E6mXGQ=BCC:S̓9<<x%H m֭5k: ּ(qJ%9+֮]~ &Mʕ+YGA `СOb޽P4Ǐjmu-cҥ wvnn.,YӧO>YG)w7իY)3E:)Q {n:t~~~| >>(eggCOO}GI#FFll, e oܸqx9^0d8q5jȑ#ann___xuDNƲ۷c#44͚5Ø1cТEғVVV_g077ǤIбcGܽ{dUcddsb qlܸqHMM49355ӧ{8::͛7#)'O 22"uf4iқ9Sl(qe兵kbΜ9f+C"11weaϞ=(..ƨQePDzzz3f ֮] D:h۶B_|9 ?D6mPn]>|u2:u*222 qL`oo̟?QQQ㔙D" {e-++LsƾDKK^={ݻw{쁃'(9w^o ^,P{{{#!!G nnn?YGd )) ;wFrr2Ν;A}2331|ԭ[SNE޽2wL333,[ GÇ&Mi)YfYfŶmXG)bbbS8::ݻw#)͍uf>'&&2Nq\Y$}iӦa۶mXr%Mی(Ν;N:ػw/ [nK.VVӦMCZZR]𗖸8=zT!edd`QfMq߿?9:J]vBXXY&ZOOOL0s8ec$$$(ąGE-_CCVHIIUPF uԁBCC+-M8xS0Y1CZZ֮]k׮h׮lق\9)Dv`hhW~vErr2<==aaa͛7cHKKѰa/MDѼysb…HMMu) ---,^}687n݋o߲)0 ?~pqqQa̙PWWgC[[ pW(qhر@PP<<<%" }1۸x"o2d.\X ѣz:'/^ L>u Pz2Jb̙3-=8Ln:h -a`` ?z ˗/!lL4 FFFh/,  cEd@$ƍ5f̘:u`JqTQQ̙!C`ذa8y$Jp 899 ǏG@@?~ ___~q\l޼͛7DxyyjhӦ͕#GÜrh޼9qE?]3 60i$Q s%9NI$ǕÐ!C~۷Æ SJ :ofaժUF^侷Xx1޽"7ŋrVݣG hiiÔ=qQQ(77GFΝ"SyT^]طoC6mڄOb…( i/b IDAT***bbbǏmJDro:::pqq-\&k׮ؾ};233K.͚5'OׯG`` J[ ۷/rssqAܻw"_\799?LMM?[n{.Ο?WWW)*P=zLa 4qeҾ}{+77i֬Y(}҄ X "ORxx8D"]6 ###ruu@z\͘1tttHWWD"ݸqO>$ ^z|rz,F5p@jٲ%3qa4R۶mp_q5@.\~޽#CCC~{?WPP@4qDQ>kdcc!yyݻYG$ӣ9s氎RaG%JsQy„ GgTVVըQ|F Ϗuz=Đؐ@ UUU!///BpB266,¤ј1cHSShԩtMֱ7oR֭IGGEFFF$ ɉbbbS"PLL *T ?35w\_~IϜ9sHMMJ %UUUJLLdEa$%%t(}/Jr'//lmmE:}8chhH_fETVl:7{YiW333@dooO~~~t]Ct?^ ͛@~bBNϏ444}h"СCŋ_u/Zt)YXX@ ={Ҟ={*Es^Ǜ[BB'O2)rwwjժU􅅅԰aCK(JJJZji&Q8 ^8)Ƞ-[Rpaٲed``@2ݧ4iB"HTFƍF4ko߾%ccc={6(ճgO֭ iͤ0iӦnCJ7.-- iȑӧOI[[V^:)**"@@cRRR(00\]]zꟴzlݻr¤|b3f QI$ѝ;wXǪ[nN۷']]](S~rrr"200 OOOqPڷoOcƌaS>dggGUf H]]>|:ڵ+M:u BNz۷o};rrrpsa$$$STF>>>HOOǺuXG+W"??ެ|"::gΜ( Deq_6m8Raի={ <<k׮e_E50yd9QVV5kd,,, /_B,C$ÇpwwlmmXym>!!033ðaÐ 6 ==hҤQZjaɒ%Ls7۷o:""" ggg屎$SXt)&O pZn7nqJOJr\ŽyH__Ν;:}t:L.\HzzzSQŋTZ5Zx1(H$Ժuk4h( O>4dөf͚eO~~>HQoFtY&?|)88޽{Jw <޾}[:iӦK[<[ե1uOL!H(** @Bj׮M>>>:RΦ_~tuuK_g~qWk !{{{djjJ^^^c0+Рf8啔D4j(QdjŊEOYGԔf̘:8uOS-K.J_/;v~u ٸq# B:z#tq̌u I$Zjj֬YѣG#VJ/L[I(RUU%d"}}}%HDcK.>"J&L '''RQQvŋ?/^ƍ$VZ4}t*ٳN<,H$VZ1۟S~߿'j֬Yw1`jڴ)?_Կ18""Mxx82qRp5 ...ضmXG :::zG._vIeM3gn݊DF[n֭[ òe˰tR$''ؘu@DСCeϹsbÆ q,--' [.XG>|׮]CeJJJpYQ/YGQ =BÆ 1m4dffx ,,,`oo__5XG޿~ҥKH$()))5k"==ZZZ V=o߾ݻj*$%%W^1c XǓ{!<<>쌁B,cٲeV,X'3lvv6<0}XQxXj>}: q_XWE9svvFtt4> GGG}u$ipթSR,ۛ455ƍʕ+ISSRRRXGQo߾%ڷo(}/Jr#)))diiI֔:NtRקrQXXH5"www)&M$!eddR&oߖK<>}Jڴj*QV~O>2cԩO2GQx{{ Rk.Rx$ Ջ&O:Ry-)))HTF @&&&(|8LLL;v,ծ]}"ѡ`Q8 >SK, p3*xtgϞEѣGŋwYUY||}uҥ 8JڵkŋѱcG?t!кukݻwz),,DNN\$ كE!)) B%%%hӦ ^ZuK$D(*3~Q "@P~DM(@*?|Iqq1ك~ <ϟf͚}>Dll,J;nNNN8p lllqqqXhΜ9D"B˖-1tP 8mڴoooÇӓ{1crRq%tׯɓYѯ_?8pC aGu ͛7ƍYGRJB1} ZBYHL IDAT}ϔ*AQ'Y9$)m۶A$aȑزeK/pߦGAttWҥKܹ31l09- ?/{_quF1{VH$Hjf)#5UHF*[llH#v 9GH9>IG5>i\\c^ܤcpvv&00h2~zzS[qP8tՖ'vXYY(aqUUUqt.!!ST)8BsǏ… y&zL\xZjqeU&u\VZ4mڔ hؿ?{%22 2*μMPPݺuoi4%,, ;vڜ44TTT4Mƍ6f!(e22J S 3 33 S SS sJ7:Fƍ:u*.]k׮L0իg^R8z(;v`턇ceeEVh߾=Zz --`.\ȁHLLD&QZ5 @ΝO yӧOLJѣGdJ,ɴi2hB5vX,XsXq2={ZjѤI֮]+u\oĈ:t([iT$Ux3M!UF1|uV-F}QTy1cw_wf=3ƩqEIA0Rsԯ_-[PhQ#vm۶>}EF:tHjP׮]9|0gΜ#}pss#%%#GH5iiiԨQgggVX!u+ׯk׮:L{{֭[_-٣G>|HPPAT4h@… $A}޽;Gb_^zKcR5$I~1LHאҐnjq'}SHFF?ed{5<8Y@)B)oLP*K  .ЦM,--ٹs'e˖:RQn]Vʚ5kyŋ2dgΜɓyr(Y$GTJȑ#4nܘ 6m*Y7oބ'zet˗ZjF:u([,wC!55sssmF7gȑƍqPM[nSNnwΎڵk'u c{se)CBBPTԩS's otxzz;( 4 ffft҅ɓ'cggߧ ӧOPcƌaر{|ܿ_l*ٳgiذ!ᅲ:VÃ;wҺuk o.[3U* *_L|OSdn:Cd|/Wue`PPTNaBr*)l"$`߿O۶my ;wȚ5kիo93TB޽={ pL>]͚5C.s2Mll,+VgFJ*hѢl?חYfq*TGW^RGVKǎ9}4ΝXb622*U0oMElTurٿpCCs/\P1,b\Q3 KN ϟ?k׮=z 6YSVZ;  &<<(2^^zyf:vh8Q5j+Vƍ=G ,`̘1ܹsE~:}~~~1B s[[[2dQӧԮ]lˡCL̙3Yd RG:t(=ܾ}@9x O>TRTT#GR֖իWx$g)*NVR5y}d/^Hr+2SR<㆑]to .Vg}۷/:~]bcccĈY~^WtB||<3hBޖD͚5qqq?$FݝfNto!..]vIE/ZOW4sNFQ+ƞ -FE6QPk3%M0U"eN iiixzzn:-ZD-ZÇs֭f͝9s b [  o֭[9qk3 sssk~?֭[T^Yf"(WC \M!Zwww KժUG?^(zLYx[nf͚X={J0=z4 44T(ZLL ,^>}H'[4 gΜ!00 Ezz[U(hZF/fffm;U%*9'/Ihel HA/^=D.DJ0 fqHR1gƏVERxZػw/޽ +tugСCqEZ-2eRT)Ν;L&]vӇVZgB3qD~Wnݺᶐ^v-<|ŋ_! C8p͛Yf1n8?Nz ^oͳgߝij$8)4Z+  R^]QiTP % ()fn"|"Q\Dɓ0aBox 9}TR$$$7$''ӬY3={dzZcݻVZq,gH;wʕ+L4 pm .WX'G f{cDz`N>M*U^{SN\~0rlSߟ;w݄ܨSN<@'|?xBbŊ]bSU<~q3(>-cL&}-GO¨]6Ν?:NZ{5)ANyrJJ4TA t@n$  4={/ z4sLy&%J`ɒ%xyyk4Att4ڲ~|prrښ;w꽯xݻiժqgϞQ|yƏϨQ{ӧOV| 7PB׺ukʔ)òeˤb* T*ǎܢ544 @%N{u 333= 17ټy3_5|'Rɶ7nPR,_T*j7x8E݄4Udu(GwbckaB邦X@H+jmAF8vE!&&̳N9ɓ'IOOB ѴiSr9M:uٳ'_5'NLJ۷oӽ{w&Mϝ7ydΝ˭[2X_~%;vO!F;3֭KZضm'bŊ1eeZ :9{iRɓ .w$66VT*Yj᯽ƌڵ0]3ڷo%֭:JTfmB@.Vɹ1!.ؔU$s?S99s#.4?˗/ӠA|||HOO`A24jkkkٸq#5j`ܽ{s璜7ȑ#~:gΜaĈ|tڕK.l2?Nj8p $?P(_ oΝٷo111Wʕ+73fѣGzOУGZ-+VI=899qdI\I쉌$d-FVEbkטT%x7x&V K}>|+W">I;v,-ӓ"Et!Wټy3~~~zkԩi]+׮]c֬YRGwFA||<; 毿bѢE-ZԀ_~+JTXٳg3}t:Dpp0`ƌFGz)RLMMܹ3V:J߿s!cFsѢE)Wýe+ZwF^|;›~7s7Uj$^g3itj׮MXX;k͊}{{ҥܽ{'Nƍ^::u :pN:ѣXm˗Yt)ޞfi+bAе"E0l0ϟoW_}L&3iaX[[dԩS9x ֭ ŅÇK4Z y݄TT/,QdS J$nbSHUݾ};sΝ; >=HOO\r<|۷+?ϟɷ~_nzu~N=y*U0d O"^^^رׯvJJ 9ժU(;v,J+Ξ=Kɒ%)^8w:RWLFŏ?(u>3|pƍg~[jEBشi+WIXX5jKAAA3o< >ׅJDDvvvRɤhǏ֭[.\ }Ο?I&!4 )Sm۶Hh֬ׯyۥbiiɒ%K:j\M!]1?-lFPdN!&00vѦM-Z$u<ZMՙ8qqiݺ5۷ܹs:k:tȠӬ\ٳgʕ+'-y=fΜ(HC~ܾ%+++)իW%N%&&"!\_͚5krDքt5>2uߑ1L(^46,\?ΝK||| qtUI333q3f`͚5TPooo={Ӿm~G7X-[$66'OO!̞=۷sLΝ}Q~}BBBƒ4܍4 i[KH8$'o Q<̌ٳg3dHzzԱrSN`~gشi r֭[#_|W\I899ꪓtA2x`5j_-u|oİlٲ̯1RJ1l0 E^ȉUVq=z:RƼ8SRw͛79}QrDq)zg).6 Z{D@VmQjT7ܜ~7o)Wcƌɷ|(VdٯMӧURlYgYflooojׯnb˖-b6mJPPd5|DR5 B6ID| Ay:މ 1CeÆ Zmۊ!--~J߾}Сbd.ejj͛Z*ܺu;s cҤI #+V 44 "{Q [[[ȴiHJJȑ#lܸ_~333-KKKj5 RG14&OL߾}YlK_~8+O>+%uJ*vZd[J\IA+®ZMv΃ zpcRRRNzz:{S7,XCr ƍ?L4x#GgЕ__|(J zɓ'ٻwNڛ6m6mbƍM!{5kFDDo6xOTX8"FQ>9%)()yPΝ9v4hЀp#JSL!""Ό3سg3+X ۷odɒ5i$֭kTg0vX .uoooYp!^^^Ӯ];c5KKK|7fCf{;ZAxwRbE~wbccaڵtE iԨb^ IDATGs.^3;wH-ZPLOG'q'!U} ԉQ,YoӘ%2 *4*UseZ[(ǥɆ _漏dT*rgSCf&KVeCTTs_x1A8tW^E5f͚/!j֬&&&9.899Q|y'~???~w߿ž&qy8;Rϴ 9>ą:bMsBƤ8 MJFa$u!oXAx|||[F0| ܻw-[fy+Weƍg4T DVDAH-^ rYݻ'uro~پ5&&s2r ʕ+9{k+(yغU6nh-r"ItT"ZQ2;`7X4EAўT7rq")ܵ 3fڵktؑѣGSJQTRrcƍ _|ݻ җIIIIt6m$ FYf$ԜN:N 2I&vZ.]Jv툏:Qr ӦMcʔ)TT͜9Ǐ3o<tbŊ:t{Ѽys?SRJ:udY3m4"##ŹsF,X)SPD d0777EӧP(6l[^:'Nd„ \prgϞe{n!zALL :;JD#v2@~7zQ=n='_. W(O4ZFΛC| ڵklْSV-O1yU1{{{kh_oٲ%իzKȿpttdY~ZGDDD{nlllPȎ Rpa<>U.>7gǎ=VŰ`ǜ/i()yg;`Q|[nT\#GO&My&ׯ筫h̙3:u*vvvRLJ2e0x`|}}Yl.]:ѳEɇom'ݻ7L=}TԣeڵkVOyB#jY~_~ox}Ң%:%lٲ,YׯӤIƍq>B7W4h{{_B6l0mۖgCe߾}l۶S*>c{sd]^Ƥ5qGUqtt$448|ԑ/^dٲeY:kȑ0vX\rRh޼;ל6mʕ[nNvZj1doqY֭[?)|gH嗢/VVV 8rVXי6m~b}7d|n?O5(nfF h.QMHZoccb& = /rϓrTejbopjaOh͗Ur4 OO4׾|-IaKɉfeVw?ДDŽm#aqhU7m;&e!QGqsXݓn?p;g#xv˗gɒ%?uҽ{wj׮mm8ZjtЁ3f/BAӦM {_B֩S'J*ҥK?x̙3Yf NNNH'dWf8t^&ߤkOL7l(=Cz kG}{okT1?;mGqho[{>GZ ]a()P2ewww/#IٳL63gRF,=ܜYf_q='O?ØҸqc"""^{ݻZ oooJD)_ω'XdI 5 ҡC 4uTĤ$..Nzu-/_ɓ)P???BCC 0xRӺuFrr2;w:kZNQt6- 2G <4P̥*Vw3͛7ɓ'z9Y࿏UKoyBՌv[cM2WjSSS//l=K.? AJ,Ɂ(Z(͚5ƍM>RJJ_=ۛÇSn]osN<ܹs]Ӻuk+%}||(_<Y~ΐ!Cpqq_~Y-ΔԳ%JдiS5EAmdPAyr։\zoծmkن/B%fBxGOp~X)G~_Pŧtyؼ:m[k'T$A= ZeE=Oʾ@vBw,Շ.n=Sk5kdÆ ;v… _ҢE  M6e֬YzŅh]mǿsy@@d„ b##WNJ(}tvB%cW(l@W*Ӷb%%,QMS>Qj=. PF sT@ iyc)) fϞҥK5k:t >>^X۷Y|y 7oaaabiaccCPP%KŅP=zĊ+3f RG2 Ea„ RGB2~x:t耣ϟ? .|r y(y֯_ԩSZ.lEEEн{wvmTHT^ލcZͣӧ<7+ܼΏM9ye9J|,@?\-WK2h2뛱A9::o>IMMё:eu5={pYSn] ,(v-֖;oXPPݻwL4lhB/f \17/yLmr"Y^6ߏrlN{ovQOOO<ȩS_>.]:=z_~coo6jԨA=zQ݌rʊ ׯOӦM4hVVVOhl۶M6qذaaaaL8WV(ͩ^3f uԡs~n 2e SNŋzH7/Δ4N:hضmQ2`hIMNF*w*X hHIh%/筋iWφY f7j+! ۫_9l*0G^+6`OQ9s!^i׃kr>U'ޓG 9 VEH͍SNn:Ο?O5RGY~=zE%hܸ1vvv_r6mиqcV\]ilْtBBBtnQSŻ64b%s46.&'ɰ6.KL!Sҥ ftڕ1cÇ'&&K~t[L0y3-BA)X 7of̘1(J͛'iVX7>}'N_~7ޢhѢy(9~x4iQ r9˖-#,, 0] ҵkWkTX"[yѝM[zѲ|*gubY 9?7|~A&ѽ{w֬YիWiѢUVeƍHOȆRJQV-ojifB\~{mx-D o9}_o-'pT' iaiZl 7Icv%6m$|J.憽=ƍرcmۖ |ɹH`` Æ c4lP'm֫WG޽sE0IIIуCҧOm۶(7c+Wdƌlݺjժ֭]vW^^zlܸ=z aqEvO?$uzBrڵ|VQTL2ݻcmmy˗Yr%'N[]?3UTy*qC*dT}r=ʡswIQhJ|bSɧjQ 0?.lۆo& -bzۗOr&@ܥ*.w ({$r-6 @$ܛc_4%+5Gp̞F@mRb/rϭ/5fF? l괣Aws[%u@E+9ȳѾֱCR8/$DIhbe5$EH*E)%3ޚ瓢d2D25jSti={6888`n.!QxYi 8 ꥟%K?ӠAW>ҥK3m46l؀wPBRrܹ///)#cRtwϟK`R$RSΥE?T$)XTҨޓ1n0s^[T`i1g9|̜Sưh ̕Te,ذaݺu|% ;s :uBTuVjժ%u$>9s ] ёKҷo_-ذk.u놋 6lpr=>3B-\~ׯSL,=øi&:u焹ǺuoIOO3Nzz:3gΤ_~Z%RbŘ1c ,̙3zH䵢e˸y&'NKSLƆ^/FO>EI͍7J5f 9. STKniy~a#|s)Pd߂$} (q-"wbccÌ3e˖ <>7%Jзo_ΝtÆ QT={V/ [DD)R@>3jժݻ&|VZ~,(YЄ% 5b*lr:~]sl~5d@"f|VL?2KI))B;wN:Rؼys*tЁ0Ο?ovʩSt# cǎ?5kZL׮]ӕ*Fj9v&&&:k[Э˗3p@]FsԆVy$$$pĉ|G%$$ƍK磤PR%+lҁpwwgtQo~- :;SȚ+W2`=zdtg-ZNG%8IhAK..ԋum,PFիL0M6ԩSqss:` 7nܠJ*_.]襏%J0vX )<<777J(޽{3';-[.qBcDFFR|y8@G|'֐ YrYXA64 dKڵ9uUVI&+W}v/_n$ cOНYfѾ}$ ;سg...ܹsGg9&MĥKXz(H1Z͌3qA@&pBXxR/ya x)cǎk?-ZW^ :TlaؾU*;vD.m6AR<1!lwBII0rWBsdVSB$sjժlذ0ʕ+;...HM0+W_1o4k [[[_Snݚk׮q  \rT^];1UФtal 9BsRlADQR(VwfСxzz9gذa4m`*UiӦ1w\L.±c駟-[r prr Pf͚Ŝ9s\G'Ϻu눈IF1oo|6湾(̙3e IDATsov͝;d|}}ޗEIi)RwwwIpMNNܹs____wN:u(T*+dbngf|ٸ! B iӆ#GtR>Ltt1sZja= s9 z6l0;Ɖ'Ҿ(J t\]]T5j%v(+ڵ# @})4(Qz6e ]zBs)B1sԑJl*GY~=ԭ[uQti#FҶm["""8{N FC&MHKK~;ܠm۶$$$ѷo_oΔ)S=z4̨OHHnݺ۳k׮!VvԪUUVdj֬I XvṊlmmfȐ!RGɱdzpBn޼ }Yd ^^^:uڵkOcT|y̨Q_xzz#cbbҥK\|9jJ%e˖B T^5jPBj֬IRwBZ'H SV-dFzz:w}}vy?dzTZUc=sѹsgNJJ%IÆ cݺu:o{֭t҅)ѣm'''l{Hݺu#>>^-˅JxxA&&y-̔T.Y7 … tڕV^mT,^$.]Dݺu7F]6;vM69nߟ!CиqcV^Mɒ%|ùs献/nϞ=nݚg;vжm[TV=z0~xHTT0zhhpqqAVsq9 .o߾RGw)Y$;}T*wyxE=zdTF W iӸLV(?d`"SҜ͐gaNXLLL[j'QUVgοjٴiƍ֭[ۗ &dw֬Yׯ_Nm߽{er!\]]uڶڵ]13{։^^^ʼnVr1ZMɒ%1虴* bS 23dhZ,MT.@<2DQRHHH`[~SJ>+֭[|xyy1m4IL:)޽Ŝ4]]*!05.!%2Fٯ]cb#+u)v] _&bn;UH)%Iasdf>S>cR缆},]]ta4 Ǝ$\zw'JR=r탣;}]HH&L#G24oGLL5j\$}#~'Ȍ3thu$888`ӦM4iV2 &&&+q>J_| ;SYYv]~7n@yy9'^܉֩S'nZ-w]ZǕ)Ur8P&Ү^Byi p1Z50T˹EEEx{]n/~/?1RT8x ΝwbXd{/c.B[[[D"\YYYa>6?b̘1ؾ}[ ر#.^^zi)%K<<<i}2ŕ[V"8M?E lQbծ]0e{쁕#9T* "11FFFxJP… ؕt:h߾=BBB0vXcL8G x74MKKLիW%Ks^ݻ#66V#;sss! Xkh۶mc:{s:t耟ތd7niӦd`Jvv6ZjsO>a:Giݘ4iDjj*:w֝5uAD"8q~)Y)66CţG[1xUʧ=VVVп|>+((@||<\4iP(Y4E^YrJPT @tp=7*÷=@* 777|}*Jc֭8|0LLL_ӓхAO^]VuҥN>-,,Ć n:4lgƔ)S_3fȑ#HOOWb#G`ȑ(,,d_YoP(۷o{tRرo@B6999#$$(T<ɑ[V|JUCM\ s#Z3 Bآ$"1g 4cM\.uMJq}\t񶲬'i&ܹsG+?~iӦ?w}&&&ؿ?+Xo7k,8pCΝ1~x[NsyA"@*2|HKKիWu՞233q{]Ԕ={/4c\o-[.<ٳ;w. 鯩msXÇXv-֯_KKK̟?'N6}6ڵk}ONNlllpi899m\VSRRH3g ,, Æ 8C Ann.7o,mZf VZ|.P <ɑ_&GBW's88dr`ihb"߂-JX,p!ɰk.|gkXt)R):wy>DVVv튩S~L @+scʔ)˫0Z~Vg}rX[[cΜ9ʜ!!!8q"N< ggg̩ b1mۆt3T޽{ȑ#pssc:'7:w nKU[mذ+WĽ{RkTUUݻUxqǎ_Yx7o0|~H/Gll, HPTT>PP!C3PP@Ae ɕ 52<;Eo56潱=RĹs hܸ1\\\ԯ_[b޽ѣ1k,kQ544K`we˱co5j/q}_j˗/ԩS:.˃A<~:uϪtk8p qJPL*(PRoh ]@Á!xhb̃!lQbiEqq1N={`XfJIIAϞ=l2̚5Kc[pp0NSNaLmٲ3gDVV5ky1n89s͛7GZZb=oaFӦM6_~+WիhРeҖ-[pB0 B:u400k׮Ejj*q8ts=zRV<666<&ΰݻ5hL&᯿B\\␘ׯ%@ P RGOnU*ogRܳwz\`aC#C yqkHܹs044777XYY{$888fzMvkwqM,^@޽|r |8ׯGUV011ӧusRSSq,""t  @ppġC ѯ_?uZz?ҥϛwt邯Ѳo>7r\ |si 8'OԹe2;w2G|||pu3 ٯ<1??vJ! :T*U(P&WTB &QÇ042DuhY}󇀧8x\4C}=碾sa\K&!..QQQ@~~sE֭[c˖-'Oƌ3ТE 2۫ZnË5rY̟?OP(Ě5kЭ[7z;v쀷7ܹ5 ?e佾^Rxb9Y&LÇq0!JѫW/d~= ݺuCDDKT]'"%%|GVuVe,GAA:w 77u͛7aggk׮K.Ly-R {{{t g:]r={Ν;tLj#tR3\n'%%`ff[[W햤cŊq޽ \}.<.ܗ; V:-JX,ݿ&MBLL f͚%K 6`֬YxbmPVV={iӦ8y$[Ғ1c --M;ryJ .vBڵkݻcZzUVaͅY<Hh0Eaee?Sχ ĉT+ZO<HMMՉ]tмys̙33gd:K+++VVV/ݬ [nC8u ϟGTTp%ѱz7d=n۷#GDhh(9o ;;;lܸ&Mb:cDGGW.))5J%ݻÇWw=ms_RR߿hذ!ڵks_Njs ˱sNӦMk_m6m 44jiӦ?N^#-- #F@II >={jllj gϞE߾}56K;uڵktUVVnݺLaF+~b4JRQpp0իWvJW^}deeQ hZHYdhhH˗/g:G!++x<Zᆪ Prr;}~rr2Ӈ iҥTYYᄬ7ӧ35k֌߿t tת$>O&Mb:;QF4|h޽ $H(88|}}I('C|>T*Fsk@ 3f322(88D"5lؐ'/// b-eJ"p8G*HLJ7oNeeeLG 2$ RͫP޽… LG9J IDAT$J)44Օ|>q\@<|> B`JHHx煲2dffF7&XL2LÏFE}Ux~i.cױcԔK|r\.Wee%eddPDDb$_> {dnnNIb(99 EQ˖-JKKI"W B ,kL&1cƐN?ʃaÆb RIR/^Lm۶~^իWSQQuZee%%''SXXsϱ=:::bW^?GdccCs.:}4/eӧ'|XJ x4f*//i&Ҭdz!oOGYu [dX:G.X,&}}} 焇éSJEdmmMLǩ?~LZ]_xwҨQD"WcBL8tt\xiڵLGјf͚ц JdiiI?Qޛ\.Ν;ȑ#vR|qH$"@@zzz#HBtiӦP($r@~~~$H>ڎݣ^zQFԩSLǩjРA޽4u \.zEAAAnq r9eddD" "PHM6.VD"0JTQQA999E<1>z7ߨe-[P =JJJH$#Xk}^f=z8ݾ}(:ڶmKnnnLGar왒,Kg]p| i&||gرc)ÇGt:iXlrrr`jjгgO"##1}t<~6m{ ˖-/q^b  !!}a:-b:K-[UV!##M6e:{ N8A1Gm.\O>YYYhժ;B@vv+z5j#..ͅ P777XYY1QpuuCtt4:tt)--Evu1G) lڴ K,ǏJuTMzsO?ʂJ>!ЬY3\|.]B^f8991>J۷oܹKK?3rrrвeK5%dt9طo  ByfR?\+++̝;?#qtكdhт8Z-JX,V^^[lW_}7B,#447oD&Mvpqq1~x)r=z4~'WQQ'''… hذZ-//իrJt7oF޽26e!!!Juwtt4\]]QZZz畕HOO7P^^077.8>[|lݺ/.˔J%\8DFFܹsr+p0U'aԨQԩ>\k7mڄׯ_-qjL_+V˅nܸw9\]]1tP0V{٢!ɪ? B~ hӦMyU2 ֘9s&.\Ac 'Oħ~,]tQ;mڴCкukFrbd~M4 ׮]ŋ3N:AqX[dXBBB&L [lI1gƖ-[pE1"##666o„ 8|0.^m۪}$L:Ν7ѨQ#8p 6mӻ߿{{{`ΝLQ+h۶mc:s̙;w"##Cm ݻcpwwg:Zbʔ){2vZM-??D"AQQ|>B!B! Rݙ >> Bjj*ڷoprfm߾9r$BCCaddtP(еkW888`LǩU=zlڴ 1c222K.~)D"F333#0@__rBK;;vv,Q#???ݻx4)֬Y///5cR,_~-6n̙3!Jq2+66CEzz: OGu vvv`:`,(--EǎaÆa֭u]@UUQ\\ .~LGz:`޽kݺu={6"##1l0CD yP(ɓ'yyyyÇt7:vݻwcرLQwwwr8p(ݻmb9s&q>qiܼyLy/r999ccc J7|>:w\.d믿8$&&n*^J7of͚亁%K`Μ9Xreځ1cF=S$%%a̙رc!P(pqDFF"66J| 0rȏn* ļy󐓓?NNNxak۷AD]ҭ[7[ ܹs8p|X4h֬Yt,]\pAAAzOqEKs8q9VXٳgktʰfbn֭h1cn:,]x1c رu&7tjSLATT9h߾=VX3f0畊+++VVVԩ PPP;w3̬n |  xqݻ8ufC렊 |8tmOOO# 4ǙSkEFFb̙>-Zzzz(++ɓ'HPj\.Ν;R`hРkRJJ ***kҋ bפ7>|xu!CGQS2.8qƍ b֭ӑ c1'OT*Ebb"QŶmei[dXBii):t?ظq#d2f :[n؟>SNСC///dee!66Qە">>QQQAvv6,,,0h B 6]aaa@~~Nӫ)1b222p!8;;3IcO8;;C"@(2֪X,X,@ ֭[ѳg?W*8wp!I&!0dȐZJו_ʕ+a``Yfa̙}װ\v %%%Ybm0bܼy-^՘Be˖aҥ9r$oV\;v --(,5:qB!nݺv1lV OmbԂ ̌>|>}ڶmKP: $}}}:s Qj۷oktrѣSiiFzXԩٳnj橍233Ǚ^nܸA#???ʕ+֖ݩ}$˙V=Fт 4>Wee%eddPDDb$_> dnnNIb(99 [9r$kq%JR)b Ozzz@~~~$H=_RRBk.W$%%QV֖n޼t>|8ۓRd:JJ~)xq8@Օ(44R))MTR6m~qvEtm999Կ222 O?D666L`B KKKZlQ3uT233w2UǰEIrrrĄ֮]?/++3g%PHZNYJ\\\,|dcc"J,,,(##CcRIԤI XLLǪ5VXA7ŧ۷áH|͛71ڵkr5)W&cccQx$J)44H$@ ===@<|> B`H$t޽w4}t<FԨQ#@IaaaTXX B"HcDBԯ_?zqڵkG{a:JT*iƍdjjJ|>N>Ͽ>+9::X,7nh)ut"===ܹ3}xEEE$J),,I$V/Ա"PH^^^Dtϊ+̌j< eff1K$ YZZRǎիLy 6%1XML`D||ݻT*ӱt=y{{3&L@fff~޽{IOOO'g F:Ed2ؐ;\. H$D^^^$ Yf7S I H$znGyyFhdL.SBBq8266&PHbR־_>d2ǔm۶>=?W?~>יUԶm[rssc: b,K;w8>|>_.SPPիWtBΝpBpR`` Qj 6 hlǏmذAcsCNNs+nݺE(>>(5&vA+͙v1@ŌHHH !===JIIy+**(99žkjbbQGGG"XLi&Ω222(88D"5lذEaaa\ݹs8;v5MR?q8 ݻdbbBׯg:JF$H*) ;v$Ըqүk zgϒq8D9mKBVZ 녝gyxxP?h _Ԕ-ԯ_?266[2睰EɺKTR?plVEIӜhug%//:sޯJ\.WJE:t)ShlT233#OOO͡nׯ_^- )11H:租~F֭" 7nQjܹs1p@rrrb46Pv{ϝyr ?wUpp0%$$\.'.K`: #JKKI"W B ,#V֭M:j'h̘1d``Q:gϦƍSQQQ۷o3ҥKkt͒AAAAH\.ztPPPn 㑗j=ãG^jlg[m&<] v'Ќ3Ԙi"sssҥKj5vZjٲ%1X2m4ر#1m/6biD"!Cb5#<<>>>dž W_9;Ǐŋ󙎣Ӣ0bN㗔o߾044ę3g`ll94)>>~~~HLL;Ѯ];c鄁vb:ƈ#e|wLyo7oބ].]0ѣ>|8Ξ=}2Ar9rrrׯ#33)))HJJBqqqu]t@ @Nѹsg2ݻ͛еCm(?B@! ! c۷oGNN8q"??#F@FF:ggg#1m۶7.]t:oGϞ=ghѢFc=|ǎCTT;rtpss=* ߿ӧcܹ033c4^^R9|svvvr䶷vQwwwGEE9K݊cǎ~Zzҥ?pM4 !!NNNHNNFNQeee֭tV%Y,rrrBzpرÇ㫯kf]d2899 ϟGÆ >3!&&FcD"Μ9T-[}m "7n/^6m01Ğ={ -š5kpٓ8%??VVVÇ>(**BFFFǧ7"QYY zӏaÆfQW[ny&:tt(((@||<\4iP(VRzBbb"zt WWWx@ }}}5{EAP`Μ9"K^^sʧgff жmۗ`bbl6mٳSSy!&&/_@:={ccȑLGzos΅D"Abb"QXR`ccI&! 8#99͚5c:.{q$۾bh@ϟWۘǏ'>O 4u권wܡMIII!CG-"}}}:}F6RIaaaԾ}{'OOO`:#~wקG1EmJ%}dccC>d:{ {%.KW\adwKgK=rRo:;,&&FK(jP(H*X,nI*kJE-[$|0DBԯ_?zqtL&#b:JVRRBpח2R$TJ$YXXH$Pi߭JJJH,SÆ E\kރT*P{VjPTTD&&&y}͛TmyX%ϏŅ2}t0`1X4c С14ɓlVְ;%Y,Nӧ,--.@&aժUXjڷoM6Qsh_AaXx1qtɓ7o!exyyul) ٳK,ANNƏ4oޜhZ3f"66(jUXX={1m IDAT11c:;366֭[yJ%:w^z1ʷ wޭԷ^رc  ѣ~8oEeeen XDEEA"|>%!CjkDL:ϟǥKRc۷o7FP1I'bĉvq괭[bƌӧݫәDTTN:Fi4?ЦM,YFSy&|=֭[ʕ+r{g =zx ]󒒒7 33k֬'N]gϞ#^]2G`oo@meiNjUJv$bڑ#GPbbHOO'p8I\qFp8tRXXHգ7}˗/Szhڴij[TVVƍEdllL=1K㪪̌6ltpR`` QKf7ٲe Szz|G|> S{Ǐ׺ݒ֭-Z0㽕D"!???rpp dbbBBb10Q#=JrrrT*!??Z[UJ%u֍Ft•+W]vԢE JBCCI$Q  ]ܬeeeq\ݻ78qHj VVV;+ I H$zsCyykLHH 57pu.>6 b1P~(--Hj!hԨQL`iJ"c:FL:,,,4z(btN߾}/\dccCD B+󪓗կ_3V\IfffolMXdmmMNNNTYYֱuL& "###:u*eee1KcO"mڴ\.EGG3uؑ,Y9e2lْ|||>\. H$D^^^$ YftN @Vͣݻ3dddPpp0 >O$H'Ąnt"h̘1d``@vb:Nx3g0P\\\{n'H"/5oޜP֭ˋ""">kwD"P(b\]PWO ~yˉÇX5I ###ŤT*63f0aϧ-Zo¶me1mbtʹsЯ_?$$$ZWʕ+ѹsgl޼{ g!;;/^Dƍ(B>Hkתm܊ 4sUUۇKΝ;3f ,X:0MsNܹs(5a:tϟGǎV}#~'͹f#--ƭ*++ׯ#33ٍ7P^^Iִۭř&bСZ}]&Mݻwt">>QQQAvv6,,,0h B 6 -[d:ֹb:;Lj#CٙHP(DYYΞ=[+[W6J ,ի1gXB+-* /_n󚘘sssB/-?ԉ'0o<$&&˖-C֭5UUUHKK{:V… ѭ[N1c M?k~7̚5 mڴݻaoot,jݺ5|||0{l4֭[С0x`۶(btѣ T];wXd ͵&ѫW/t ѵ8u;pƌ4iFmN4 8{,:uꤶqk\{bʕu @уhj쌶mbLG(\PZQdwqqA-m6c +Vx?zFڳ7ղRkk뗊]vEÆ >LLLt#F̌3@)8>}* ݺu>rxLGeƍ1w\А8o WWWx8|0Ft֭[1m4| V}6$ "##qqH/ZͣK Sb`:Ϭt~w4h?3+^_WӧO8::bݺuL=ݻw1i$8qsŋt,RT066Ν;t!!!LGQ ߿h֬qX(btƝ;wжm[ڵ d "bܹP*X|9&MUF* }֯_t 88r\j,X#G`j6S* ʕ+/s'|t+//GFm678w} ;vѣGu0wqQefWQD]ܝ[h ~ʵLZ:FM"S --RqTJS\qdgfYrޯ׼s||G͛m߻ヒ/iii`t^zԩS߿ҎG???ٳ;[jCG1tPʠA0dHff&cڵ 999£>qAT6'[qE5<ѣoߎ֭[-2i$1n8"$$Etofo ggg5 yddd`ǎLߟ9s yb;cǎ&\V‚ X<#b˲ 7oބ71l0HX+V`y&Ė ݋ѣGcÆ C]ܛU))!!!s̡ۓV[ RTTrׯ8p@lIubӦMIJ,}bKbvm6qgY m6zG 2~&Y#d]&qqrtt7xCl)2sL=zM:<988Д)S(**((( ޣ7)Jhh4f}b1l0zGŖq_:wLjft::pT* "aޞJ%jJJJ"Alҽ{w3g2jd͚5$hҤITRR"&OZZrŖ8}4Rիb1C׷Eס,,,$ZM...Ծ}{!N',_ %''WyYY%''S\\j  d;PDDj49maΟ?O#G$)22 ŖdU;F… bK͢xaa!ӸqĖ"Brss!.={q@te%ݗ?qرCl)6g̙CY!J4m49pqCQtt4-̟?u& yfbV^-?>Y͌ h4CT*lGR(66: :$ZqttuY40rqq!GGV92g޽2 EEE0R$õyW]vTTT$שW^ԱcG:sr*LjBBBarttP\ٔ,RTP([n'$QHM[&FCAJs>WWW 08JJJPN#ZM Kǎ[M駟@ZJ4?e4W^y<==ƍbKhHNI FuHP۷ŖR-GEbK3f3bKBi/;;)$$ϟH6mPTTT+2p@zŖ! o6d2JLL[J,Y}^YYQ|||y''JQAAAN*d2p%M`?~2jΝ;-naa!i4RTԽ{w@T*)::.]dZ"&wcq\RRB'OnLrqqŋ-ECA[N\tbbb(44r9qG!!!V3՚\tÉa4h߿_lIgfLBɡ$%JEG,dbbbRQ5_Կ'Zݢ1o^l644ž={aĖ"тjJJHH4 F www {JJJtR|Gg}'xBlYբc!==Gؒ˱`\v*=6mXHeX|9VZA0m4̞=]t[ZJJJcb˱9DgyDZcСC%UbժUxw]繹:{, CBk=m΋/}ٳ嶺&SO!99b˩6ѣG1`;w"!!^G~T*T*1l0ĂhZj jrpM?iiiغu+.fᅬhSl9-B ={[Mq|}}a<3u:˗[sp…JsTnSV[vܹ/V-²e]v[MyYl)6Bբ]v7o͛'zQTT>} 00bˑhHNI @ǎyf[}r ̙͛7c̘1Oq6''  $AD ñjժ7sL|w8tzm-|/pe#eiP&Q@BCCaҥ EGGSaaO>֭[-CtE3JKK)99)WtbN!!!Aj)--A'L@={lc%!BAbKB||<S:^OIIIe,DQQQ$1W&{{{Qk2i4ruu`MGKbʕ$UޖFYY;())Il9:J111ͶV`8ԩ9::J;w-&O0 ?NkZbY6olU]999ti`)<<PXXX^'{{{oŖ"ac:D?[JҶJ4&$ՋfΜ)`0Pll,y{{F\ݳgrZ`RƓO>IÆ kp;+lb۱cLjax͍R- k֬!gg?qRXX󣈈< b~gQ_f d24i%[n4uThJKKVZɓ'Ŗܺubcc)44 qg6IMM[)++hrssVZ5ʵtԮ];z뭷|}VTU3(,,޾`hh42PC6mڐM3g49ǔڵ+b˨Oƍ[INI 9q-ARTTS.](..NlIoaꫯĖbq._LƍΙ3g͍¤16$77CIJ,)J#^oS:uEٴ̏?H _|ѠvZ-Q||}PddMaRT8 qqqIJd">|8iӆN>-STTDA^^^@Hh4Ėh1n߾M*cǎ۬DzzڶmKZNߟ͛geUCQZZi4999$"$zGyz[yf8N,S6m[W^y<==[fƅ䔔EQ͢իN ШQĉbK2qe<0_~e瓷wՑC]v:P7߿J9R}ڳgOi䐓}bKoA %;;*F澃`Y%%%4yd~z+QAhcUi%NAA <:uԬ z8@*uFUVNqqqTPP Dp hvn^FRREDDЪU?n82eg@nj5eaT?ͥH8jr)͉w}Ė!!/^$ahΝDd o)$$~ɖ%mh0DDkLg=oKHHHXCK.XnR,o6m.\ۋ- gڵkhR"ف8ݟ: ##F¬Y裏 j=7;;>l ƍGmιsn:|(**Riv܉#;;V飩2sL|ؿ?ׯ#55HII1A`` za'':fddd //yyyaoob磰<#;;mSC 777Kjkغu+ EQXX{bΝPTTOOO5 JO<|||D'Q?J:WFTT"""xb%n߾`8;;#11bK8رc6mڄÇCP $$ CvĖ RRRpBlڴ J˖-C>}ĖeQJ%C up50 9s`BVVq!y //D%K`b_Js.\PeR7z???<~~~ꋈs˲裏aqiM#GsΞ%Qw {{{t6l@II k׮ŋ/hS-sAff&/_wwwF_>}xꑐ{NI [O2{X@ׯ'___rpp~[4'z&L@t̙Ji֭"?`,0I>>>tر0Vڙꫯ=n IDAT=zԚ%BZz5׏P߾}iʕORǎ-fS̜r?$ooodXMPP 8y9s& \NǙ5hР:J8b|IZ|91 # |.^H<_k0())j5)Jda:u'xYkit:rss+VXSNQǎ߿|HB,) 8Q);аaÈZ瑎NT9ldd$)J`r9믿N?̎y)hv~w@Ν[iԩEr.]jSmڤiȑ0 mذZ$$䔔YfQPP2lFNNT*;Rllh4) O)))ԩSJƸj.,M :+_UqF%I<wܡ !ԵkWR n߿?ƃN4h4MT*m۶ 8F*bcc)))\xh̘1TZZJg^0 C>tZwIǏ>R4}y1cAZOO@Ƣt:s bHTZjmQ-gʕl5kH&ѤIK<)))4a„J/z"qFbY/_.cڙe𠰰0=N}jCmڴ!RMj׿U4UTcǎZcYƎ+%J^^{G 2VWbKo4|% b0詧Xh"jۼys^LFNNN:DErJJHHF׮]ib˰9W\8 @{EGZZyzzKT=(j#''羓?GݒgϮ⌐dRԯ^yruu%\Naaak׮zE= rttliwJKK)99HVSxx8}M!!!Aj)-- C8ryzzҨQ*Ed2좨naUQ5j^&=-\Ҝ2dM0QLL  ???8ϯ|AuJĹs@ EEE0RD ̵k(<<6Fŗ5_$Ƈ~H2:$QIKKhR*9X&::]&:SPP@jCS?lu5jiᵎ ̙C C=Tl b &JEg^ld2s &JE111tDԘKP۷-pwtUcBxxx{G5/7$w}G (44a„ZuM2999Թsg=ztWJJJHPdd$oߞcԈoг>}=*}.׏!eiǎ")5rirtt(W^!"cbMMq |FHAAEEEuڵVgt]5R 622bbbH4 G%t HDjj*9998EDDLkyRVF䔔ŋ2 s@*d̙gFz;H.߷VkrbLptt裏ZY%_UIX @{^xHPĉi۶mw1|PP)* \l… ԡCjT|Zy{{WEqqqa6Զnݚ(&& a߾}n޼i6%cƍ|FN:E;v$:sIX^OӧOѠƲ,M4<5M^M߶m۪ոaÆYxq)jy6mPll BBl]Fbҥ 8pAeee")((Hุ8JNNniЊHVS=5j(Jrz뫌u2:t`͍7nPll,y{{[???8 /u6K6<4|a͚5$hҤITRRbedŊyR8q"uؑŖҤ1ס4 $JyhzzEVK+WjYy'///WI˲4uJL6մ p2eJhtYޗJJJXRTURHNI Q+W-s 5ի@m @Խ{w_,|UƽNa[îbߦb'x\tڵkG^^^:t@tyЅrSbۤRH.gN>m[?tPw*ŕdԫWz0R\\LT*95ۗ-[+**z!JaԥK:/EEE0RMPDټy3d*tI15w6mh̙bKit::pEFFR07աoI&Qpp0]zbssskhKuڕrrr,g}jbv:2 C=\aaay7NƍOKK33$FCd0s΢eAxiȑ-aSgGTII M}#_z%8J;KVV Tδ lo[-)J&L0;L/{{{;JJJR{`̘1-yuV𠯾P|| 慥^LJ~7o˫KKKk.lٲvBAA~a28Qe͟q@X?0yd\~̰ǝ;w8b333ǛG}ƍR{5<쳸z*KHHO?=z`hݺH*%2i*Ɨ XppvmZ]2FG@αPp \33>3{{Ǣ  /8pr5:G||<._vڡwܩrL&XzuVg:u 8qΝ;BUrF… k|򵤾N 舠5Z㦕`ö*i6n+oG Ӻ4^֕ Ib'X( xG>`t:Ǐ,בZiM7o0[cUrZvvv"T'_ z;+v2mXdTp|"zߟVixy ?.9`+wn"W3w)Y5}IB>HNI ?矯sT_K%##III8wΟ?ӧOܹs`\r z{eŠP7~ՑP ܬLb)bŒWm;A9i u+zG#;2Q+_82ָPYqƅ]CŽgP`!--RTiXVV??Jm"V PÇcӦMشinܸQ)j=$$Μ9ݻ0ޫO>$֮] bB0uq4Ɠ218"AM|^_?S7뜿%}xAucMPꞱer4 5oϱp߳lo> TՐ`عs'_AP`Ȑ!ݐ׽P q={]vmP[K,/)a}N:޽{#)) AAA׮]_~&L@lldlzEz uƹhA@Q:A4 @;ECQ|xg6 L*~,y2s{G߼#GDAA=*9>n_ L^A0ѵkW޽;ЩS'}@rssqA<䓘8q"JKKͼSq޽{El]y]17 t5:L : 7 6|{qfgpwk)֪W˕;-\ė^S&i=܌9VBrJJHH؜5k`Μ9FtJܟB?ΝGs8{RN%Rz ;~B>0tU\X'g ]EіB^G`0ywVn 3.dF}=ϊ֧cRѣlp/)~wܹ[n1c 44?9e˰`spssÆ c=N@΀BEzXg4 ̋4|Fiq1SO"AlKZRT{3Նq|:2 `ǕN< 'Ggxg\m5\>} 55EEE޽;ƌ{ Æ E___L>Eۮ NB-[`„ 6S6ڶm J7|D bѢE7o,Y`GD0@kX7@+`0;Lami)x"s|8ٳgcҥVIi-!_k@AQ3 _{{o٘BEYIYf)r_5^{5 Z >>>xc/Ɩ-[ncKFF|||pA4>#Dx駡qFغu+֬Yp8t!L;Zs$̀b8E*Pcse1pSpspspSMYYONӱ K.Edd\jP5l6t+QqqHK'wE`9}:\eYbԩV_>} $$_~ڽ{7ƌ>>HHH@@@ؒ-Z!T2 yw4/-lZtrEmehm'-~,\| .\6mڈ-_b֬Y5~0 )sp/]%ހ8;3=<䒣6O$8qj}… URkVL[iYS*tWzϔz_|///^G^@~yV2czwmy)%j6J4O*c]UUa ֒6V6}xHHH<QRRMbۥzqnyN&j*4-ѼPy&^jpXFQƢ n n:17uKJJjuwT*m^7٬ku"zz̜9wƚ5k^"Yz]P8weJ4^*: ƈ Wa֍LL^߿50mꔜ4iVX+VXY B#a{K/A&aƌC qDbʅi1<5ZA@FZ/{Ǥo/IJe-3bh48(ce(adrF :(G0j`_J\pXkΪ(cᡐMCY25~~UQ(ѣzQoߎLWǴoaDFF߮.L(u<鑯33}ȶ: 8@գ@7MpY+xyQĐvJJHH؜{bԨQBVĖc ۥ:dqXR ҢP^wN ֘Ϋ1Z)w؁_.]2ƪYwͭn.iezdcƌAvZ#Zj:q1."TE: Rڼ piA<Ai Pt'9;wRj n&ιs{bĈVO>Att4^j~$lڵk/c„ (++᧟~[V@g"-jQV}+40{v2sN|e˰h"\|J`e0e)#Ę^ vx*X xEΡ/^e˖իptt[N%_ӧOٳHMMř3gpddd[KX J7xr㈀|iu $eA2ΜU#,!aDrJJHH؜'OO>8sLx'KUŭbqpO} K`hH8,: _СCx7qQ,[c :aйsgdee၀tݺu3:wlNTQ*x衇j*s 4,˂8 B%g4{&7#<j,1R [#ϡ=F'LJ٨hD &)gכS@=nkqHQš'_T\,(Cz~d,o9:ڞ0HNN{gm۶59$@& *  D֭}I!Mv۷m۶=7778;; prv)P+;vst#AgrJJsi 1A c-R=N >nѭ];aʕX|UI7n܀ڗxuV"<<1Ma^^N<~@2Kt_w|edS Е"JWPDr(zaEOA=%)`A .Hlgwg7_/ w';<}wɌ+*{ .m"5Br)ՆXnϟ̙3ZA<{jp"šRN;έj.uqMRFZL425ʥ^?rrr[N8(++􊢰w^Ӌ>~|ْ=O_ʱ2nmr72ٳ={vVT[hP Żh YHgp,*Lj 7j~a֭[v\:v ˘1cسg :zAӦMYjHJւbYƷih:㎖z dJ6n6}Y.]_Ѐe.. O*@"ڭcĘGK/ĸqHMM;zپ}{+KLCtqb/-8!P|+j <22-l&Z٪_uLAݻW0j̭]"s7Y!Pjw=|[\<fEAQ^U V VRt9N(rgw'! | 0E.qAqG .]бcG&%srrz|!lѣ1dff$I <իWs]weS5.v#ߎCѐgEz.%bvEeRvIK&ɖ-[2j(ybBv"'ԥŀW!1vEeO]vFŚii3풉?3֭~;z9z(7(g VHس7Ӻڥt#.1ILj!|mdr"fYu63* qAgͶmFy7;-s!Ul  T)&>9̾[ckgfك( f3eee߳gnɺ`S%Sb;8oXt8 G9l3ՋJۚ6mJ^ݻ7:w#}S1 D4_SUWBVLjvP.DRR]{zQv7; &!%4#1+UCw~W6nȯJQQlڵkwj:sJ]l;QF%HuN.pЭI9=rH^t:I0`OIOOv9߾YYY{A2e:LHZl8RL M38kȑ$%%駟2k֬#,q3N[72H3HC ;9PYa1i۶m|'|GzRo=zرc%6!U&Ŏ Tq2vi,GjBI BԫW/lj'hҤ+r)d+]>{D5uxj2ZNP>j5;;_Ţc#rCKaRn$DOn)&lWrt@\]t nF?77MG4Νˣ>ʬY7o^Uٳg")qɯJQhrK-uqNhZ~?`+`ٲe")]d{^UśH!"f#)sqEHu^>(;w+;zO`W P5M7PK+rb5ѥITHY&BHӧaC<e>THӓ6}O8V*dgU6m0z舙qd()) 9X}_3 BV*"ߏm2d_ߏ $)ΤI7o<5&$}%OuM4ilcy䑰D,r@QTOGn~8ToKq*f+]Ah7oNΝYjޡPTX}v4}Ah4ɝe:TX<.Ucbjj6pভ5ͅ5UC4~;^ʆ#Ÿ9r$n<3%5kc ː!C ##ɓ' @fff ?-aW],&OՕÇ#2?&T y:"-Q<(B=iii\}zRg'nV,$D).м9YyDRR\tE|pɚ"J܊.4ԥ&C%N @$%O9Eظ{|5 |hs6˳^]~MO&d#;!+|F)⿬ȑ#c~;Onn.DEE؂mٲ .'NСC|pvClc~ᰛ%+NfN.E$#iO}ih5y-&(JXAaÆ/pq]/GKGC> _p0BxnUc]n1nU4!=C\1zR4l#u3lfm bBfnߖJKKcǎY5''G,VXAhժk׮%--^ӧV5"p<\ -ANM 0kgw6f=hgKօo΂2(;Νg5\w(i˱v$ʞEFS_.~)IIAtsea4/~v7Eߞ~P_h '"{cT1޳Fc oyh-9M1bD@"),XQ1b+W$99DzX,;NeZl 4`G^~\ο[nܹo uVfPPGuɺ4Wu-[Xx1=PX͒|CN1(O"R?iP(S$fLS 4:111 >KH bѫQ_7f=@_2 xl-u[DtA՝Bi89\}~FŦM8|_瓛K-zL?4McΜ9L63gxbVkۿLJn=Q&juϖPM$~?Quؑ'Ncmye1@莚 IDATBHz54ؙ_ >٠{=z0qD]_(vQ Mhޡ%ٮJnV 9:pj!:pe ϸ#qì>ԇ_gh>69̑;)⢴ E[9)ѸNȮL<›/~Xf6gY7h<spqb_}-kFx_4d2 U>UV:ǿ2UrvcD ?mf[/y(&2w'YL-N?߽9^5iZnﰮ*ʾBށ||;H Qe P==55}ㅲRʾ"mLOQs>{~Q/}뽩]w抱Ptrqhv9clz)i% 71Ll+GSI]zj WiC WiӦCdi DsO~^:E=Fy?+|y- bO_׼zl==x?^bֈ4eXt'{kx'%ײoʿg9/>AV} Z:eMeTM#ĥb3'9( fb ><(C%NBmHQf7sǣ;Iݏ2@) !Ŵà~4^}sѥf~O01KVI6@-;{ aqpe*{=}}%;>7j^_9gTv{-cx( fb.U_Mװ޿z\[TMC6OR8~{>DYR}~?>x?'z1mB|]RӋ8bV5 Fe~ Ϭtj`rV$V߯O:'%LZf"4Çdٲe Gh4٠*͚5#55̈IJϖ뎮 U_g.ֺ֡Ouߠb/pQCa+Yvy66O|'õ/pgG_rˀp i}:҅OgP49ܝ+3d;odJ?.j>;dFG`-PZeMe$ f Nfǎ|A9;_7({I5qKnz 񒛽L7+k}z2yO+iRR OA[7M]/BL:k\.5২_Gb}S!w(8>^}Vo. B5eǪ3 {/*f;udUUJW-~ach4mV&dlCqW!IsmurhqܪR$\tuhr/Qlݫ,ldoDw֔2ixS`~5//! +V`РAjՊk$!iJ5MNGl_]!4I*8.WMY63[ցCY';IY`X~9e/Ap6m-멧\7 F@sq:D,M EEE̝;3fpgP컏Ϭ3>V/`>왬wûj^=iU#HVqF/t: iذauY̟?7 ս^t2Cɮdd}|R ypU4eY$)M>^E"Я' ?9Ym B$̆aiӦDGG78)i1H4@)Kjr9G X5&* syR7.w;x,5wy2r1S2,XQF1bV\Irrr@KJj#9SERN<[j_]s@rsqL~6}Zr*s Sۢ/eܴ խVlzjBOePMx =PPOQDJoZ\wEoE88_I =i̲נDRRɲwϱcz. hj1xb@0}vPq%K|RHR+7c'!rZbDB%@E.NVMj8[q~"5?^ǓdryV׃VnTWjL43{+_9yFt Ұk $ b NjՊ#b 2%BʒUM5( Iʺr98H^Tk]YDOeSy$û<$Di޼9O~9^nnnq}hƜ9s6m3gdX:>` F(HB9#-!kX؂M r73>66WpK*Bڜ;OH/+?cZAŪ&C.m^c/zT=Z*k"Id ncw_&)))fQ[H){OJWȒ2y 5ԶD?D [_z:#)iyhADEEo\qBf UVTD[O_4c?w;oȨ=y3ݺ gZ!H|d5APgTsQ1jyP)NF S$≉@9@Pe';^u]^]_{dd̛hmr:wx/SAAmJW5̦]a 6h@jׅ֭[7xQhi3##鷧G$%r*[=>4C[h^/oǸ>5߹ 8˨(ܰͯdmCVVuI jSO=#S8'*k*#I6Ƃ)SЩS'f̘sB(q&C9>]O%g LkؿZsUC}Oi4IC\;7Ü9sTFII SL!%%EA4f"^}UO=d Ϯ`WT]GiGjs6v%3Q)idjC=вe_>s+e-Fok[ᤙ :ǚ8 ̠hkJ}Mi)(\'I c4;.<2(;X|>[XRnI)X`c^wV,RuO߼y8:%}MUKx-L0-Q;MnCg*ɦea}V]տ.Iдl֬YÞ={t8}]QȘZ aUߒ3_y1Mۏdm Zf[k:WS: 9)mz>f;k/k\Q-ְWvExR:d؞aWȒD~:r.f*˾222غu+3gStBmr饗?_2f̘_eVXAQQƍ KNE#Y;ϖ-Ts8W}*ss@CfIJ,]gyAmɒDQP;gW욠pyw(v-vF6}غr 'KK'0e=2& c&~̴rGd6(;X֩uf=f TYO=۰oBƺ=.z>j1=eMU,LNJ.Z%Kо}+P$ j$8tV:eO`;Wˍh]ިo=ռ;p9g&J%D"ڬw(aGN4oޜUV1tP!瓚=Ã>s9Շ q蜘+ˌ3HHHع-bYSKPUQGXG8 fyA,xs ׯg,ZĀ/XG2)ĥߖO=1~B(BS#62$ѽIT@FJ{;wۃ  <#<ڵ+ds;B/ 7x36P}8+[FTBiӦhт,3-!6 6 ʒ&I`%z'ݻ76mjqrrrL 49s0m4fΜŋuOHmۖ6nܨw(A:L$IB U ij%J}Sbheod׮]2`7\0E q֗#_<:m:XRE &IfVqAIHns3qĀ/bLk( DXT$!FUHH{J<|oz$BsՋ:%KNJ)tLjVA$AH!FA-Xp_[Vh yQM"jOSQLXt)W_}58IIIݏ vL’%KxwѣGGtB<׻}Z>ւ,I)DG> #) [5!i&{1{FO[wS[<ڄa ěcLM1UHHY=]tpw`#e]UW]7Lqqq+YjgXxz&E]RT߄p [2$:v;<(**%CoE})1%N V.nOj\p˂ۓؠ%\>i~ V8Ӗ-[ 8q :T*էOv;X 2Rbb,H"![ߔl#Gɘ1crpd5tk%9+ފQ$'Lz' gpp7rsw! boo;PI[&4Vq1{&*vBfffA(+PPPܹs~nY1DBd 1" g6b4[&,>\x(zIqbEP> $yqtN¨Ca Is7o1rrrLXb UV]4CR>}PU/{f.jG,,tNgqtl6_~y@(.iOhbMn.4y$ RM,ZʦO>޽{Yp!Cde)wz$E`5!Y 2 kO132*QQQ 0+Wr 7;&ASO1}tZ$ Z̴)v)d;_ĥ X_EieŚicfXmpgzjFsyFG`@=J (O6oMC@Ջ~? @f,Xӧ3~xӱZzTmےƍ2d$KtmEx -rzHH&#ctlf{=&OLtttPDj8 NbNܪ&ڙBX%P5rb-1sY_|~;"mQhk]2ʡ'N\ wIQW^e1&Z$qIi$yׂ G1yd6nܨk6dKbi Q)sqRnM AwJAhn32L(XKCmPDX c--s]$ԅiH B>CYY1}BŹk~c999$&&"i̝;G}Yf1o޼Y wde,%";[[b'ɁQh|_]#5΂5HHm۶.~hD IDATl[h$FsENR5dIB| dZFii3}j몸\~L2EpBNQcV] 9.ȳ@=B뷍2hmy$a!$ɺu߿?;]t v\ ͍L:u* ##?{3h {D}L0nݺl2ARRRx;%,9])usFP]HDHJIs7^{DRJMYTPz>CN:=i6$Z<1gBVvsφ tۖ#(FSЩx\ wpXA?x>KV\Go6g6̀ TU. 111,Z;X1 TfdٳyNex赻U ކcS^X1:} P%X$Z X 1FK/}v:ww8U2R-i9N;; ꜜY';,'h14iii\^?+o 0}tƏOzz:VUgϞdeeF؊55y{f?)rQ5G0IHhhRJ,x޽;&L;m6ii]] BBK-+f5EO3_&l$bHڟ<7o 2HR`jM.t*z9=mT_#|(KĚ ę Ě|eߘTZ\s5{"))BHHKK7gĉz VL L orzF*qb[[-U'rGFI"d Lř<8} BNz-V^y睧w8AT4J %.bBWRiib-CiDI&dfO~$CG66)@OCЩPV*t`H}Ġ%ytB66΁m6v͛9sjs'N ))o/86v)Sdy&OwH~[oqwSXXh̭'3] v7ees\(+ V02{I% SUcG'QV)q)ՓmMZ,y>beg[:$A@Y&(]26ce8@^+>aʡh=eJ[Ů($(=1*7m̲LI&${g6De 2) 0`XpHJ 2{9رcY~=mڴ;3$#3ͩh)*en[+UT\ʩUOM$':N7A& c3X *x ǏwamVp I{/T`4?<R.7z3z5JI)qWQ.otL;vh4}:%%srrLZe̘1ٳ wH~׽{w?i߾4zO +}UgKwKzYQ2ecQbLlFQ-?ƍ_Eh\y=<2Ba,U-C[^7&)S6ɞ6fIkw$O;(-?n7'NYf+z#ԓ a1IKrǮh`2 vE;A/MlkRv`[Yd<e,([f5g5&%Mܹs;w.M4 tL 52 |ߟq?FeH,{PTh8_i5ʎ"Ho4cH$N-.8w덲E+Pl c=_(/&!!KrwNPH!jj`WƟ}jQ.M4I):ۤyr]fΠ3^+I$ y~nݻbO6<1Ljj*;v")YaF233IKK;֭$uVԉQ<{Zά:`Oǡѓ(U_N+}3}O )had3w^*錓 ޤAQn4{U|[n={ 6ר]5bS;+7*Yzs hZtUuL,a1zۙ msmPQd[泦Dr.WOT4\g`CUq*PuYedR5!o)gR5%c%IxOSFeߟ=Sc\%[JJJh߾=3f`Μ9AKv\@^ϱZz(gĻK6s*%b].3Kȥz*cnՓp%"W4_VJ*sTAfIB<`I${u !!17?^d({oeL' iV$L<={w(aG.o-/\zJj.go cSDĝ1A̙$qz- 2xO$a2H$kLT> -'̾+9UW]͛Yp!W]uU`+cѢEE1uTeo0aݺucٲe$''R@iӆ[oٳgPnd}:g}߳ƭyڙ3/ݧxZ$o@0xqśPq~;ٱcIIIz#zj+~צݚz=6'a3NS6ʞA'۝wP5&oY}5Jŋ3i$.\ 7ܠw8B*/e[=Yy7$*ix^h=*QU_Z.ҩmZϪ=ʦʢӿ''A)iٸ[y뮻t\ ұcG222>|8FO?%11Qž,yg_LO]C _;! ]\vel߾Ν;NX1HE?fۏ1sK4C1af͚?O )CUZZV߿VBsJ,X?~<1h[nlݺU0zuE龊hڵꫤP/ ƍ馛뮻DBRhQ Bպw"IoF A^zj*vM~&wޤ3|^u%\B6m%,[cdžlh4O3|FM^^a4vYe8{Iׇi̙3iӦ1sL/^ I+)v)Speq СC;O PGg<󔖖2&A:ٳ'֭E\p<#8NtrUW#prJŒ,\wu,\EQ'"+ : E뮻())aGZlw}s=s=G~ؼya :yᇹk0a;w;!L2JDXt)6aÆJݛiӆ'RJKK`:uٳ;3 FBBޡ4gꚔ"zi̙3iӦ1sL/^VKRZZGZg?䬳;A"O'i9.ި6&׊~ݻ{ҽ{w#8J;'D&9x ׏aÆ)Gxyy1n8ϟϠA#"׻WWW:wLBBBfeBlSzu|}} a>|zQ|yO.'JŪUT9JG!Cؽ{7/_V:Jo>*T'ʜ7DJJ?9,,,O΅ ]69r777#H...ZRLpyڶmKVd"zԩSCHB!2{% %..#FdV&!vxzzrN4jԈ+VtL LMMٹs'fff4oޜx#}.] *% qJ22g899Q װ|7O2 WWW8z(ʕS:R쌡%xOW^I&TR~)_NBd?V7|òeشi={T:B,ޯ,Xj+V`׮]I!UF -ZDxx8;vXb >[[[ׯ… U:xХK4ґDf``СCYnQQQJ3ݻiԨQ̙|=[[[߸(VJz{{ӲeK5kF```ppp͛JG" QF,Y???LMM$ԩ6mb׮]|JGBE2pӳgO2.Bqƴmۖ-[p]mۆ'NٙZj1k,\tTJ,ɯ߿qDףG .̏?t<# |цRLoTsN(JzӧÆ cӦMRxCJB[nѨQ#lmmٳg R:"aÆ8p={мys# !Bփc…/^vɓ̺[!h߾=?Ԯ] GB~غu+=R:xC5j`0c ̌d?~t}Q<ߺ5#2g2c֯m^+%_>{e޽t]H]t `ccC@@JGBN:+VG*I!D6il߾???Lw-9!իWgȑҥK)Z(3f̠F.]}yfbbb,2зo_ F޽ T:ȡ+Fݙ3g|Oׯ_'$$ƍ+E2gEoRL[IW.\vrȑ|8+ӧO"Dp1իG}ğґڵkiذ!5b/^\HB!I%իǏ?;Yn IDAT6mBƆ^zuVݻǁꫯpݺuښʕ+wG||ґſfϞMNСϟW:ȡFAhh(Qr}QP!jժtȜIQ2$$SSSJ(MO@@qQʕ+t\TRt7Q*D~m66lHݺu`JGBq <޽{߳qF-L%zŠAݻ7NʊM!DVqsscԩ9rXvMƍ9pm۶XbԮ]ѣGσoT*V^Mʕ_;LO...mۖ~A(ھ}pssH(9|uBBBprrsiٲ%͚5#00+++#jJB… ܹ3ݻwgǎRBdp4h7ofڴiyuB˒$_>-ZҥKY!5 *DV7o7|ܹVZQX1TٸqƲ);w@mۖ#ɓ:tH(VukFddjQ2))n߾sbz///ðaشi2A"E͛JG"Gj 4Ç3}tVXZV:";x 5j޽{;v;*I!B(Vٱc˗I&P!ʊN:|r._LLL ۷oqƜ:u www(Y$\ .锎+Vp:uFQ:aԩ'NT:Jt9bcciذQr>g-\CBBLQ2))]2c |||9sDvvv]y+D~@۶ma9RHB|`ʕ4nܘO>'NPB# !PP%֖ \!x+++ڵkܹs9v:tȑ#TEnJtt%Kk./^W:aM?>|Eʇs&ߴ(]LTTg޽ݻݻ+)yB7ԫW3gΤBt=ڶm2e ;wH"JB0>>iw}:t%Jd&"h4\pcǎq1?իWPvmjժEZZ*JGi߾=Æ c̙J9L͉-|WqppP:" ZWґBYR2|޽{thтgf"OQ|ߟuqՔ}ÇL:ڵkS@*T; .$((Ǐ+ N˖-Yv-gfΜ9J9̴i8uQr#G駟*#os& zpRRRruր\]]ѣRBRRTzsҠAjԨAPP$YJEӦM]6gϞBdKQ֖}ٵi!ȳ *+#GϏh\ڵki֬waĉԫWERR%Y`>TzrE鉷qDRzuڶmѣe Ν;R|CmΤ𲐐\[e˖4k֌@IQRx!;wfԨQ3;wRpac !򰐐7o۷oRXB!ruvnٙ#GЬY3֭K@@KBT*ʕ+Gr֭[>}:4sY666T^='H4h :tt$CL2*UcKxBVSfMis&+VH߾}9w?3JGtŊ#666n߾I4i'Oӓ3fHl`mmMtt4:l;WٳtؑGg5jt$!DYj=eʔٳB!KeL?T\9#!DlҥK\xK.*+T@P‰7dV^͞={_qDѥKΞ=˅ Px\g߿3g(%Ws& LLL_lʔ)_W*$%%ѳgOvի޽ґ?F#REKU7nZHB<,$$>}pɓ166V:BLO,--ٷoڵ>ߟ:u(E!-[[[lmmiݺuy1119sgr~'n޼ @%VUTbŊ/_cbb.d E֭ٿ?ժUS:&OLٸq^ԩSԨQCVڜΝ;SN֮]VZZZrΝ /}vjE6mq{MH Q:B\@i/9t[nYfJEBs"##$r &u딎c]vxWt\/mΤ/-UV)YXXtdHHNNNٜꊝGB,,,055%**J(Bd&NHݺuqttٳRBdV… T9r3gJAR![Q|>>>TV Ο?ϒ%K022R:BPո\ה]܌ʭ[2et:Ɣ.]:}Neʒ%KRܣcnnݻ_>M6XXX(K(ٙ~1a+ .t9J֭˗C|'~ΤeEINǝ;wr|Vooo @THZ%!]|www.^ԩS1bw.N:E~p7NB!Iy֭[ٰa-[JGB񖌌P;vˋ-[pE} *H&͙tppN:l۶MHҒxZs燇cz///ðaشi(CLYr%5jg2rH)H !D||<Ԯ] pL"I!LСJM6|'I#! *DZUsr.^t۶m(PrQlY>Cʕ+ (+ϱOoW_/Y,,,8q"Çwޔ-[VH9㏕'YYYw^ƍGNrIKKKt:UTTR888`ooO||< '|QRR={dǎн{w#YXX䉶B<ٓC1|pL"]YϏAc-[F>}rU#!9J_mۖ[nC˖-$"qMn޼6$ SB\\\ҿwvv#ŏ?NFС֭#1VKժUqqqaΝJQlmm>|8ÇW:J~z틛?sm-_|A\\<~'O-Jʊ%KRbEVʠAH***6mڤXvssS,xQ.]HIIիx֚5k:t(RjU# !W޽{իf͢XbJBGȢ$@bb"LJQF1ydY}"F!44be {{ +W9{M6cri>ҨQ#Cf͔#aiiN͕9s:`dd9F_|K71ydƏɩߎ;hӦMυ hժjߟ`ۧt!ˍ78p Ӈcnnt,!D&MҥK)_c޼yTP+W*^2x" ^(L޿-[ΪUׯ_glҤ j5F}Z+VPlY 7 }`jje)R|H>}ؾ};^^^=ZB^q m`o޼IHH:H-X>[L;UPw۶mt҅!C0w-Cxzzzj_g 2'OrQK3Ypa4iBPP))),Xׯcmm٦Oĉh4o|E@7noJ9w'Oӓ3fjnҥL}:#FȒ<+IRahht-K-2ך5kxQx#[ne0k,ݕ$ȃ"""4ik֬ᣏ>bN_!x( -wիW>}:JGBpmq7n 88`n޼ӧO011ӧTP-[₳3%K DdիW3`o*TtŔ.]=z0n8kQQQ4lؐ˗/gxZTо}{,X0K,XÇOV)\0gϞ1K-2ƍ޽[B W^eȐ!۷}2c i(t ,]iӦQHƏO޽ Bl+iӘ:u*M4[[[c !z=aaa;vckk˓'O[T*lmmӋ%KL?YNN:qA Z-fff[]*'_;wj"99000ښk׮Q@l͵gZhyiQihhٳ;fX\\\r###J,ɉ'(RHdgΝoߞd# M^6m|v!T3eٳ'ǏwAB\y(ϏS͛Gnݔ$B3g,_}z=ܺu[nװgW:::czkX;;;Y响TUVe֭JVVbذa֭[ݻVj4Ξ=8p`ee˖o_˗L<9 [l/oddF‚nݺѧO)""%"=yŋ3uTlllؼy3;vT:"I+FN0k׮_믿RL !S+%u͛7dر)K!`̘1Ot%[a΋Jm(-ߒ;\t)}{ALL QĿc͚5}`ccCHHƙDʔ)CdddzVVj0`ڵɓ'OuJFaڵL4ƏϷ~%BO)))lܸ3gr5v)"˳EIdfΜөP+VFJB!:t(K.eʹo^8?~۷ %44;wpgҩ W[:889Iϻ>}]ta…J͚5ooo qF|}}t&&&<}JEvرcO.-FGhz= ^~yF4z=z}5?,ED.Fpԩɼe֣W52P3TJ P/ɓ'3qYvvvߟ={\ԩSԬY7ntOzvرcy&}e„ 2SYiXf g&<<]2zhʕ+t4!d+WЯ_?҇˛!J|\ 6V^i-3Zq4&&&XZZ|ve:Ni֭wQNdʕ+ӦMNtdd.`r;mf뉎`n@^^W%s%ZmBP"C*}j@Azj44lن/{g QTZ@@F*_S9Ù3g^:ׯ1=~ IDAT_yZ"[9r#GraKO.?{BL7gݻtYVF !ȕEQ2~-5CJ! 6ŋfݕ)x`mkllLb^YtrrP=<RWX`VaL\m:a.Ԯ][fJ,smO7ժUc̙4lPXB<͛,\ooo 0`XZZ*M!x/jd7sss֭Æ ?/`Μ9fU!"L!zɓ' 8PHY̌rʽrӧO~-9Bdd$!!!tXXXdٯ666%<<<}eTTaaaDFFF61116fffacc=888`mm=666affu:7&**ӧOgSj|>QXQrjFGB-OϯpL#|jժA׮]1B!|QF9sSm6F155U:B|dȑVS:Rgbb ...^bb"-]uy vŝ;wHIII ,gBKJ0ag~>|@…N4:=R[ȴʊŋbu||[nMLLTC'R!6ICb"Jm?{)OJI6WbijH1S5Ք>H"Jȳ 0a)F !ۣGظq# .ҥKԭ[7Ҿ}{)"_~P|yv͡C7n4mڔI&Qvm !vJ…ر#B׍cxxxпW_^xg4I[}l،wttTC- :PFA 0YZ4K$$gG+GG=[hђ%Q:B<i777l"aݻw'00g)..H޽KDDDzش󢣣ӿi WWZ[[SD I֖ŋceeidzk.7~6m_TX#GҹsL-=$')N&I O: TWB5] +`1LF҃… 8p@("9{,ٙocbbt4!D.u5֬Yիym۶o߾4j(Ǐ[B!%'Ou֌=Zں !2M6 W҉/00M|r-LLL|i"""xs}Y‚@ٺu+*T9 {|ZZݝaÆQ\d-=%3FMӦM%"~:M6̌={t$Ǝ˼y8vUTQ:sLoBhxs:ֱVVVtܙėn[Vji޼9cǎnݺ]x­OO`R=7R5~׊)YF*2~TRŋ+C(Kӥ_?queȑnZhB\ҥKMll, 6o߾tArB!ހ%3QڑT\aÆɑB!2]TT-Z ::=h4|gq\;jРA:ue˖lTToȈWΨQhѦ-! Bw9B*± `jhHeθqپPVbb"?s%88:0j(Wt4!D.͛Ô)S]ҳgO'B*RΝcܹ8991d뇩фB=/ɓڵ+Id0>cڷoϪUN ŋh[pp0eʔyX;:{j5iNBd>*Ps!c10{WN*T7dvYt)˗/ѣGt֍#FPlY !rӧOrJ6l؀Vu2+R!xOUR___.^-[9st͍ &ģGػw/=իW'6YDZGhg[ @?ULRV5,E̙5oQ̶|oА*g=nY!s4wԍ7`Y i Nes߶ Hйxީ?a=; _r#Ka.7_rD1e$_=Q/^Ir"ZW|¹ bK&q˚Oi/^B?%XUJ⟸||XV'> <,}?osY6:֙!ppp0J'99]vpB>̢E)P@XX۷oLJgH.]իe˖U:BoJёYf>nnn|,\# !%*W̱c011N:>sbGޞМ瞽`NQc9g|q2Bo<d_lYe%˝;wʕ+gt 8{,}cǎ֖~qy# !lmm믿^:gJGԔ۷7|t*T?F{bl&SYNT܁ʮ1u(]fQ]+ڔKfzmţD^M)+r;gfKrb+tIJԡgϏXP;/{C%HC -#jGi8jEY#=).:ի2~>f'~{.ٟcRT&&UZ|қ>L=gA\="::Zz2OGGG/_N^u~~~4nXxB,..___ZnM%ׯl޼hVX+*UN}E)B/jUZ5VXAhh('N?J*O?!y[ٵk{K.xyy)IdGGG6m֭[Y`q^DhZݻӓ?CJ 2,L-QO_vc}Ӎ|W*'@vA}l)}J1]p2Kܪ/{3}]3 j@DJWxx,}2k_^Yy:kR|H=ܹsTT)SWd/_NŊQ7nחPfΜ9ݻwYr%͚5xCCCϏ;bddtT!B%sKKK?;.{g]b+`,}F^bjcbbbInMLӘnrA NPX[? HQ#0~U\f 9se׮]ԪUSRn]^}U"##(R|w,Z?W^y܇/J>}os< )))x401!)ćt5op' 2Pc)Ks-rmf~,^>(=>#SE&܏%ңt)֭Kڵ t9sӰaCfϞ"44cbiY !s zciiIIIa۶mL0ʕ++U!DYXX0`6mDJJ /&88GGGZnŋIMMU:BbhܹE~i(ϟϰa=z4/_V:C%]QRc˃) ~ltԠ$,沤@ `ĘTO|gc܃la,y2}큵!_}`y<1xegپ#o뀆h @x oL&Vh- СC6?k^9c 4hݻ9s o&5jP:ILLo͍Fok:i$Y)BPeTרQ#yf͚Ez?fŊ`ggGՕ)a„ ٳ?VZѪU+cN'|BRRV:R>*~v=*4d5ZXRa~[G&eŝtKnBZQiжO[L'9CQdbfPUM}hayc8Dt7HK^FCm-Аqp-.S:qgEjM:ui։N 0'lN +4Gr(65jSfajj9өYeT*"p\U FjOv})#˾c82wɼ{8G1U%{ۛ %eGEc:u?k>*^xc 9ǍMƾ.sfmZ鵌}a2Mj6jEX<ؤ-+eP*k?-zsVc)DX[jpU@/88{,~) .~aTٺu+gfΜ9$$$K/n:^xK x!Dax"V⭷bΜ99r.]?3ydڷo/흅BR@etRTTެZd>}:cƌB!|w̝;O> iPRm޼OOO/_Δ)SOǎݻ7}3m'`"$.FWi`qe(NUw**j-pSKu/_άYHKK7pY-[ʕ+}6C aƌt:9Bɓ'e˖-DDDPF ƈ#th[!Ń%K9}bF<<<8q"D(Q!-[ƫʰaXb+VT:yKUQFa0ؼy3o+h"Z:7s)g%(:k1~F7/+IY?vy-ε*)ත&L 99߶x,|}}Yl{֖'+аaC !lߏ/~~~\x:u0tPFA^=*! dr-֯_G^z?qѾ}{ !PȾ}Al޼{{{#d21fѣGiҤґxغu+O.m^LLPfh 2+q+t ! JEhRѸ`0`cc;ÛoY{:v˗/gdee/HQ/͛ݻ___v۷qppwww\\\d%BQHQˋ5kpE;v,cƌiӦJBQ1b/^ www# 33ݻCRґfڴiՙz#g2r/&ٲ9{:t)9d(7kWҶz*h `zAll,͛7/eiiilܸ~HZl /)S]D\\y5ܹ3}P'e`}vbbbQ{ݝaÆammtL!BRfz={n:nJzz:=z`ܸq >jժ)Q!D⥗^sά_#t z+’%K͒Fjg[zfs%=0ΙBc?~Q¶VT-bd-Z0x`"giϊ+Xr%1uT<==eTك/aoo;SC !⡤()*;;@|||زe YYYtOOOƎ+y`ȑqFvt$|||=z4?#ӧOW4K:u?>g.}M$!N6z*R}UԨilmEJh-vÇqqqĉ8;;KLXl{ƆQFm*O(vڅjza.D)Q!%%cݾ};vC`` & N'CBQJ2avG}ļy$һ˒%KW^۷/ueժUEߛYzp5# T*& K VR%)"W^DEE)z=-[3yd(+(nݺEPPpejԨAҖU!OMdddgYA)ehd…|ᇌ5*U(K5''u2w\Νp#66uE||#[fJG)z=Z;vV>|8&MO>XXX(QQz=GϏ ñͼ U1/BMeff۷o'%%<<cRre !P\\%k@@w\0`\B!D(Tz`nILLq 6CFQ:BرcIMM_CH 8p3k,,YR8q"7n߿H[s֓'5)i*T`PdUK %wb߾}JG)bbbXv-k׮… 8880~xƍGƍ'("߿@HHHjժӇӿ6ltL!BQRE***  Zjt:U %н{9s&̚5EQ|ycX~=ƍ믿^+̛7ׯciiYd- F 1p'\w d )VGrj X֪TSRM%Ұٓ@tܼy͛7Ehh(cĈxzzthnB\ws/V86&3zĿ \xTZM%--WԪKL d2Jʕ P:N`ΝxyyoQbE 'ƒ^ɓ斬kڧOWtT!B(ٻw/~~~رd ;/*4z*ӧOߟz>+++c1k,/_NPP...E϶mۢꫯdŅz#zF} p}L@EU*VMeK *jT޿(5+cƌĉ8::*Hdggڵkh42h Ə)W`0i.By^iРQB!K;5yX"]vݝCL!(L&?3s̡QFZJVc#GrABCCiѢEslܸ .J m0777i07{c T*?tʉ|p/MRQNcA5W[P^cqc wܡM6ՋUV)PzX~=۶mݻtޝ3rHVtD!D!3!wMZZ666tNG߾}S:B!S(Ο?Ϯ]`dddвeK @ѣ0Bb$!!_|2g.\(݋tzEjj*!!!ԩSPBn݈)"hia0126622X"ꏕw R|f?~"#UUZ *ʩZEy겸̘1-[EڵSF#LJ 6Lúu=zq TTTx{{d.D?f͚)OQL&sAիW{ݛ޽{Ӯ];yoC!%Ev58@PP\zwww. ^ "";Ȭb˸akkKPP+V,}M4 .ZhO`2a0tPW+\M7/`&vmȿgNcݾ#4l`L#e?CA**i,Q j ?S=⏛JbP\JJ ;vSNl޼Y8"** ֬Yiܸ1GfɴjJxB뉈͛7\2...у^zѱcG4qB! %Ea40<9pF:tVtT!(SF# o666t:c?9wnnno??Bkk wqz+’%K#J\ŋ9q5jP:?͛!66\3f :tP:zNC XjSL/7(wԉMv߶(*+SNJd_˗/gJGz(ÇLJW)Qrss9u!!!ŋh4o@>}^qB!J)J ׯ_ȑ#H7GƹBի3gZE1qDYQR ,Zw}+W2qݶ׿D2{l֭[ѣ#J,L֭[Yn?ґyaRRRpppӓѣG˪!J$̭XƆ{gggs;+W!ēO %%`0:NʕS:BX7n`X=zҦMcy ,`ɒ%>177;;;&M'|R`~Ã?s*GbW^eȐ!űezt$~tKZZ9vXZhtD!ƚ šViѢE\ 'BQ()? bS|ypssCѭ[7*%ѣG9s&'Odڴi,\5k*L;w._56mbȐ!?D*VX`'::yVXtQݻ'RB/eff۷o޽{8::΄ hڴ+!CCCښN:ꊳ3ݺujժJB!(()Dg߾}ە+WX"...;wRJJGBd2i&z-RSS3g ,=b2xW_زe .^~FW_1cƌ٦(8t҅ {n"77{%K0j(~Ggddg|||غu+tOOOFIz%x<^ϩS8tGСCcaaA֭ڵ+...tEB!2)J QΞ=9x !!!$$$hСBQXKOOgɒ%|gԫWO>OOOcI&3fzjݻwlwڴiZ.mgE^vGVZJGŋ7n|駼E!%%___oݻ1Ջ#F˿}!?ς ##ʕ+Ӯ];sVWWWWt\!B)J Q^ʉ'/?NNNKnnn8::baat\!(v.]ļyذa={?IXe``lݺ]vȼ .вeK~W&NX)ų2LL0~ÇӼys#RhݺuKٱ~zZlYdfǎl߾cǎaeeNc 2D B3)))?~cǎSSSjmۖN:oZB!Ř%P@zz:"eHHiiiT\Ν;/_^BQl>|7|G2vX>#씎U Ə?wSNϼ)S™3gh4R<{ŋO>}#J$ϟ7'NB O@dd$lܸ3gPF   rʅAdݻGddCG4 IDAT$aaaۙ3g0Ll~,B!J)J Q  bbbEʃ[o\ٳB ((7xXL… Q:V9t{'% 60vX~) ^gҥ?֖tB_ff&AAAcz)A0^OllJdd$\̻uڕ5k*Y!B<#)J QLřDGGвeK\\\pssk׮4oJpZ!(zz_w2sL}]YRD2d޽ ZRyӇٳgh"R$<<^zH|M}ʕ+W~:k.233qttݝQFPB<Ĺsa ++ kkk-X{94htd!BQ()D q]=j.R5m۶5޽B)||TTwyiӦaeetR/;;#FJ`` =?qppgڴiR>>2K[n={?Ҹ8|}}XYYj.DO&)))_ ÇsM4 ͛77tssQB!(#()D MDDǎ3Ν;=;w6_i(6޵kO駟Y& ,`ԩR,dٌ9iYظq#ΝںSs\]]jRkW '33ʕ+Ӯ];s[nԩSGBQ`| k7䥗^ INN8p 9aÆR+ʕ+9p3"((z(^|ERnDsrz\]]2dCiӦ^g={6_2ﵤ%M6W|K!B<%(c5Ph6l*Ttd!x&|,[LoF.]zٴjՊݻrʂ)̾K~m6o̰aÔ#J`}]BCC1b?i0???)W}Ã!CtB۷osIN3hܸq,C233իiii>|jժ)Iswaʕ|7\tcDz`Zjg `Ct:quuXB/Zj888{ ֲeK(B! )J ! ܟ ̙3L*;v숭ґ~[z=SLߦ~JG+L&g_eҤIOYYYk׎֭[uBLYL&ƍG`` GYfJGXbɓ'[oagggffJPP۶m#665jлwot:C N:E|BlkAFF͛ۮ:;;sB!(()(iii>}}u啎-(޽˯εk=z4Ah%d믿槟~bԩO{EӱvZƌS)K _@^#[lق ӦM^zylBBϽ{ppp0φٳ ܾ}ӧO绿uu֥M6k׎6mжm[ڴi#B!D$EI!bnݺExx8'OԩS:u(rrrj888Ю];ڵkGi׮B֯_ϢEa,XX/f|̞=?_LJ(Xj/˖-cڴiJLFFWo%**=zk1t|-srr a׮]ܹ۷/ bBٳgͫ.ԴYf.l۶'B!J)J !^ϥKhNJުʎ;R\9 !J+Ν;YhՕy.3>駟2o޼'tڷoO֭پ}{!',]BBBt̙3?X89s V_~ݻ :9sйsgc "((@n߾y6ded.:潞9}4hZ5kf~-wߪU+,,,.B!DDxk p233:SEfU ! Cpp0/~mƎ+o?^{zE=Qq7u=zt,ܹ3ݻwgƍf޽{_cǎѼys /PV-n߾͞={ $00xTB>}ׯqJłd"!!!ߊǼT*V7oVU:B!%&&5 իWSk۶miݺ5ZYBgvi.]ʪUZ*/3gΤAJG+1֮] /رcY|͞{ٴi'OnݺEs...+WPbE# 具7 2ӧӫW/"##ͫ!8::tt].e` !!\p̻Đ@qpp0|l׮TTIB!B/RB:ׯ_7(O̙SD)K_~իWB핎#Pvv6x{{Ccǎ$''Dxx8ʕռIf2Q Μ9CFF_oߞʕ++^!BMB2QoJ>}G+۴i#++7|VSLW^aÆJG+߿?u+N/^{Ghh(=\,,Yٲe CU:("yY׮]Kjj*jՊ7n~GN߾})Wұ(Tw!66XΜ9Cll,111={\,,,3]ͻjJZ^ !BQH()CLL@w`Ғ-[kۦMQJBvMNNfׯ-FKRRǏ]عs'C?gJܹsYիWs6l-$%%QfMzNc2VJ&Ds̙3={\%M6e˖h3g?~U:wL&MR]|7b h4F ͛7Gѯ_?z-Db>~ŋ1jhٲyck֬t|!B!cHQR! YRRRyWv_z+++4ioee^[ *(^QYb˗/… 2m4<==K'O̶mXr%cƌcN3ӧOgܸqTTIxox7yWꫯŅ3g0j(ƌرcB|???DsiZ4 ^^^9RdJOOgxyyq 222=z0uT+xE$ocyk޼uǼ[֭eB!e%0L\t|ٳgW'$$߈y4k/+|B'7{rըjƎˌ3prrR:"V^ԩS$--|8oŊh˜? .pyΞ=ٳgpԬYf͚[w)VB!y()@nn.J=::/R}SZ櫯b֬Yh_qvJΛoYDğEGG~~7ǽ{PTL&6lȰaØ2e :tP:(cv[-::L#nvvv2T!BL()eXvv6W\7[?AҲI&TZU#Ϗe˖gUȑ#8q"nnnJ+h4>T*ڴiéS !Yݻ7\!h0 LϞ={~:jшZٙ1c0b4ht\Qeddp…t< !B!DY%EI!OZ={w`iiIZlѢ*URH(=b͚5ܺu޽{3qDFYb #&CV%77o`[OqZ-*_~ee-,|_]PbE>f͚%3 ɃE@j/_{a2pvvfӱcGy.3xd1>>,ʕ+Cϱ4iB>!B!+)J !(paqqqSRRŃubooOƍw_~21'OɶmbT\ѣG3a\]]zX`` ={TtlTT^^^Oܽ{JVZsenܸa^ W j231L4jԈDǴZ-3gʊ "$$hj5ժU#++{QV-zN[[[cd2crruyd7u|G!B!D#EI!E*===_2>>㉏~ ݙCa;5U*[w>_:::*XH3,((P52k,֯_OII W]uW_}5#F8mSXXHAA/`?>3gdٲe\^'͛ڵk/aB! `޼y\veqYx1'N:[:Ø1c9s&_VbѱVg梋.:#g}SO=œO>Iss3iD,2>c>3MƊ+p/ض!b;@|"u+00{Va޳2s4:k4MxG2eA\ٗ-[ƲeXt)7nrQZZ㡼vĉ8q"\p! TUUQ^^Nyy9۶m۷S^^Neee Dii)/WE`[DDDDDN$ %EDpXue2\8IDATQQ 0!!!Va9h JJJ()) VNX6l駟f֬Y2|p Nʐ!Cbvm<X7̃>gڠB!^}Ux -ZtYo~Í]_x︍;vtR3Mvsp[t#8p͍߻$ֶmx뭷Xl˗/g͸\.LRR555|>2220a'OϧGPn_zZۗJKK,+g$"""""w(F$[KخeUU`l\(..X39lfժU̝;9sȑ#:u*W^y%#G>e/~se.=ǁM{ئc-TTTYmtt @Kl.zv |?'K5Cumx-HtY$X 2Ir$;Vٗ2m4^yxƍNJ+[[neŊ\Em6\.eeedffk.6oތmۜqjȱc]`0HUUUnJyyylZKaaa,d<+PRDDN(}YSSC]]їƄn>0DՖrk̙3W_}`'Mjj*?|l#)8lZCZC:6m!M0b}j`tAձm֮X0jܤn||5ՐWpHtuQ. e6It$LRn q|Invp2oNYY7o[nl2***HJJbРAk.6mڄm >qn,X TWW۱3z|/++S{U#HHSQQUVVƪ-kjjb-b].B )---D>vj,X{dYpw3}C -Bvl :atp Cy];a0HXy,Rݝae":U`[ln`8CϿ#++H$§~ʕ+y7Xd 0tPRSSٽ{7ׯ' =z4Ǐ訞>j*++Ŷ6cYYYL[ %EDD>H$Bmm^e41률"JJJ(,, .U!}Mii)4M&O3<]vh F+=U=RVLTE"bpaf̘qgap ڵ7xf:t(444n:fь3 /[MMMTWWw{M\YYm/:>F.--Uv>Fhjj:.ܵ}!333Bkrii)uA1bAovb޼ys9l!v4t;Ѷzڿt6 2\dy] Lp1㊅cڵ\wulذa-JNNf$&&Xv-~[yPRRr'(G]GG~_7mDKKKlU{Oϧ8|^ %EDDSUU-GǶwQ\\L~~>PPP@aa!Z9"O/~Xp~\.wσwulPy2aJtf{wx4 8ɴ1x`;<;<ƍv}罹flΝ;c+p.tI 0 g$""""""GBI>*RUUE]]@ Orr2QTTWYTTD^^^7g&}Obc Dv0\.SS\n^u&|]à2'"7M~Dɂ M}}AQeaN9唣3xٯkQoՍ]coacQs9Js>:*++؇555%?''[p + ͥ<OL%:o_ϖAۡ-HM{&qBtg ?b/.]رcФG}}=UUUPUU߱cGlD)**\e""""""/ %EDDNP555AgxCAAyyy瓟W3-8Եj A @矦ӏiާ;xO~rFy|jiiUs>[}RRR*%%%౸8DfeeDDDDDDS()"""1~ZjkkUYVWWbs]wo099Brss`fggiqcǎnՖZVOu_fAAEEEx<8ٶm#Fo䦛n찎WG"(210 89=i^l'vZf̘s=a?mkivfGGG_蹮"^/]vEDDDDDDS()"""'V`vD"OHH ##c˞ﲳqG<^u&OrÔq-p7kEk;:mGxD\fgd cӦMX]X0hhhoerbm !???6:?? 4$""""Y5zlv 2? k٫0 L$!!n[oAu&l;mj-U `3fԄ87!!hBI#  HSSMMM466m]w8=Fw婧Me&spm1aB6MKVVWm? >=W\qٚQDDDDDD$JQssAWc677SWW& 9Υ7~2G`wn fG_666V/g%1z\eY<\h"""""""/ %EDDD[o3gT( `&mc\=BAҳ(N"~w_8mh⧯\0tŋY`֭:YAz{~ኈH(EDDDٹs'Hd, 02ac#9m1i-ia8-ރtlJLph~5iկhjj⭷bɒ%,\͛7cF,|qBI~qP2ڦ511s=K/K/2ޮk)<:-~V ?\NgYHcng%.OwJ ^ϛ/摓DhG+)E.6 ݂os]]/^Y죯֍[8)K/Lst̹]8|=MO2e>*X#I#{IӦ 'SȨ.%#RCƭ˦𵛦2< |o֛}/g{~R?yfSS0"TՋ;Ӹ__OhKg_s nAo95㢟ICu}c,z:}w$ 2332e SLtR,YkF}}}\+"""""""J# a5/I&qYgaYVl۠^N0s6qL[06?07X4{_H][FU3`1{9g|f]8+x32G2nUku}R[̲w21{۹5 yu82!aPҩ}|x<%dX/^ô##n;}N㗷pkG2IE|:<}bڧn-\{]/5]ߺ+U_Y"®Y|L_pO_ggr~|Y5yr?btdz7Ꝝ: w)I{ԜJJڎDDDDDDD %EDDD\MƤI?~mk=օޝɜ}>N ~-;&0ki؟gVXV3EdXϏ\rNCV8hfd&g3$#t0q椡 Jˡ13d{/egxwGTE ;GL9McLܾCnwk~?}sO+2.LmJq)ZncUrr""""+rۆѲ=U x+#ԭy]C#Om^DAkc<_]͆.7K`/}OY:.*ˋ!]L'u@T`;֬bK蛌ylrPE{?}욹p.Iy)*>ڊ9˰y&p="""""""rp缽U "5RKgM'HNv<qlpw#<0w\<2িǩ9c{6"Ta7RH4D<7=fV.除{/|kON_}a^RDDDDDDDEDDDSi yf;d0݋ wMb ?`lGk``cG3p` C̷1OP_9H%xu!l\|#Js1RK1&#ol2nkhXd 'VbgW~x7ιr콶:ę8d&鋈HPRDDD8eP]O$$رT uw<(p? _y\ .:$ZxWacۆ,˔wM2J]wX2k9[eų 5i?sp9l}a68` 䫾I.w 2v. w<x=>ze v5Uҗ:^$+("""""""rXs=<A1{-K?iCb1Ŝ:TtYz OH `UlkȨkϢT21.ĝ͠S\T}uK^_>_lZ9i_cBd#+%5+9$4Jsɩ>`/ΦtN圔a.2NKeWa}IW?IF0D'e|6$;ߛq8`3Yv:lH6Mm-\a6=^[vie !+{p3oC3qe.݌6* ۳JcÐoa[5p_mq::9?0S3uP q+UA62b|_v+>#=P?p]3ÀD7g{("""""""9}q( a11Hs!P()"""rȿd${'sD_0뜞a@zŹy)Xq$UDDDDDDDBII8+'a``a߱!G^]S=! ütբOA^Mm筈t9%EDDDN0͚6~Pɉ0mDv;äPRDDDll d; '0 p(I2,=HBIXvg'ss^ Mr3<#灉ȑPRDDDDGl P;@DC8 =$9 JHLvj eWpcO+M#o˄/ei^\fG%"""""""GBIUCG DٞHҽ.JR<&{p3RDDDDDDDPRDDDD+84vl Rq.8, RxIVU GP"8zGybsd5謈Kr&ms"""""""g %EDDD8ukhi888M$9nݸ՚UDDDDDDDP()""""GD #f?DCGPTIy1潎^$;@)}P()""""GEvh+! QEe_fe m` "57$""""r#6;av"_UUy왆8D/0HZ{\ X Z-ԌUDDDDDDDBItނ6H,$NM87.o=I$c S& * EDDDDDDR()""""}@G8B{vk ٴ#zT]: li.active > a { background: #4fbfa8 !important; color: #fff !important; text-decoration: none !important; border-color: #3aa18c !important; } #navbar .navbar-nav > li > a { margin: 0; padding: 1.4rem 1rem 1.6rem; color: #555; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.9rem; text-decoration: none; border-top: .3rem solid transparent; } #navbar .navbar-nav > li > a:hover { background: rgba(79, 191, 168, 0.5); border-color: rgba(79, 191, 168, 0.8); } #navbar .navbar-nav > li > a:focus { background: #4fbfa8 !important; color: #fff !important; text-decoration: none !important; border-color: #3aa18c !important; } #navbar.nav-light .navbar-nav > li.active a.dropdown-toggle, #navbar.nav-light .navbar-nav > li.active a.dropdown-toggle:hover { background: none !important; color: #555 !important; text-decoration: underline !important; border-color: #4fbfa8 !important; } #navbar.nav-light .navbar-nav > li > a:hover { background: none !important; border-color: #87d3c4 !important; } #navbar.nav-light .navbar-nav > li > a:focus { background: none !important; color: #555 !important; text-decoration: underline !important; border-color: #4fbfa8 !important; } @media (max-width: 991px) { #navbar { padding-top: 1rem; padding-bottom: 1rem; } #navbar .menu-large .megamenu { width: 100%; left: 0 !important; -webkit-transform: none !important; transform: none !important; } #navbar .navbar-collapse { max-height: 600px; overflow-y: auto; margin-top: 1rem; } #navbar .navbar-nav > li > a { padding: 0 10px; height: 45px; line-height: 45px; border-top: 0; font-size: 0.85rem; width: 100%; } #navbar .navbar-nav > li > a:hover { background: rgba(79, 191, 168, 0.5); border-color: rgba(79, 191, 168, 0.8); } #navbar .dropdown-menu { border: none; -webkit-box-shadow: none; box-shadow: none; } } .btn { text-transform: uppercase; letter-spacing: 0.1em; } .btn-template-main { background: #4fbfa8; border: 1px solid #4fbfa8 !important; color: #fff !important; border-radius: 0 !important; text-decoration: none; } .btn-template-main:hover, .btn-template-main:focus { background: #41b39c; color: #fff !important; border-color: #41b39c !important; } .btn-template-white { background: #fff; color: #4fbfa8 !important; border: 1px solid #4fbfa8 !important; border-radius: 0 !important; text-decoration: none; } .btn-template-white:hover, .btn-template-white:focus { background: #4fbfa8 !important; color: #fff !important; } .btn-template-outlined { background: none; border: 1px solid #4fbfa8 !important; color: #4fbfa8; border-radius: 0 !important; text-decoration: none; } .btn-template-outlined:hover, .btn-template-outlined:focus { background: #4fbfa8; color: #fff !important; } .btn-template-outlined-white { background: none; border: 1px solid #fff !important; color: #fff; border-radius: 0 !important; text-decoration: none; } .btn-template-outlined-white:hover, .btn-template-outlined-white:focus { background: #fff; color: #4fbfa8 !important; } .btn-template-outlined-black { background: none; border: 1px solid #333 !important; color: #333; border-radius: 0 !important; text-decoration: none; } .btn-template-outlined-black:hover, .btn-template-outlined-black:focus { background: #333 !important; color: #fff !important; } .features-buttons button { margin-bottom: 20px; } .customers { margin-bottom: 40px; } .customers .item { margin: 0 20px; } .customers .item img { -webkit-filter: grayscale(100%); filter: grayscale(100%); -webkit-transition: all 0.3s; transition: all 0.3s; } .customers .item img:hover { -webkit-filter: grayscale(0%); filter: grayscale(0%); } .testimonials .item { margin: 0 5px; height: 100%; } .testimonials .testimonial { padding: 20px; background: #fff; height: 100%; } .testimonials .text { font-size: 0.85rem; } .testimonials .text p { color: #999; } .testimonials .avatar { min-width: 60px; max-width: 60px; min-height: 60px; max-height: 60px; border-radius: 50%; overflow: hidden; margin-left: 10px; } .testimonials .bottom { width: 100%; } .testimonials .bottom .testimonial-info { text-align: right; } .testimonials .bottom h5 { text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 5px; } .testimonials .bottom p { font-size: 0.75rem; color: #999; margin-bottom: 0; } .testimonials .icon { color: #4fbfa8; font-size: 2rem; margin-right: 20px; } .testimonials .owl-carousel .owl-stage { display: -webkit-box !important; display: -ms-flexbox !important; display: flex !important; -webkit-box-align: stretch !important; -ms-flex-align: stretch !important; align-items: stretch !important; } .testimonials .owl-dot.active span { background: #4fbfa8 !important; } .home-carousel { color: #fff; } .home-carousel .owl-carousel { padding-top: 60px; padding-bottom: 20px; } .home-carousel h1, .home-carousel h2 { text-transform: uppercase; letter-spacing: 0.1em; } .home-carousel li, .home-carousel p { text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; font-size: 1.1rem; margin-bottom: 10px; } .home-carousel p img { max-width: 200px; } .project .owl-dot.active span { background: #4fbfa8 !important; } .project .owl-nav { position: absolute; top: 0; right: 0; left: auto; bottom: auto; width: 100%; padding: 20px; text-align: right; } .project .owl-nav .owl-prev, .project .owl-nav .owl-next { width: 30px; height: 30px; line-height: 30px; background: #fff; color: #4fbfa8; text-align: center; border-radius: 50%; display: inline-block; margin: 0 5px; -webkit-transition: all 0.3s; transition: all 0.3s; } .project .owl-nav .owl-prev:hover, .project .owl-nav .owl-next:hover { background: #4fbfa8; color: #fff; } @media (max-width: 767px) { .home-carousel { text-align: center !important; } .home-carousel p, .home-carousel h1, .home-carousel ul { text-align: center !important; } .home-carousel img { margin: 10px auto; } } .owl-thumbs { display: -webkit-box; display: -ms-flexbox; display: flex; } .owl-thumbs button { background: none; border: none; margin: 5px; outline: none; } .home-blog-post { margin: 15px 0; } .home-blog-post .image { position: relative; margin-bottom: 15px; } .home-blog-post .overlay { width: 100%; height: 100%; background: rgba(79, 191, 168, 0.6); position: absolute; top: 0; opacity: 0; -webkit-transition: all 0.5s; transition: all 0.5s; } .home-blog-post .overlay a { -webkit-transform: translateY(30px); transform: translateY(30px); opacity: 0; } .home-blog-post .overlay i { margin-right: 5px; } .home-blog-post .text { text-align: center; } .home-blog-post .text .intro { text-align: left; margin-bottom: 20px; color: #888; font-size: 0.9rem; } .home-blog-post h4 { margin: 10px 0; text-transform: uppercase; letter-spacing: 0.1em; } .home-blog-post .author-category { font-size: 0.75rem; color: #999; text-transform: uppercase; letter-spacing: 0.1em; } .home-blog-post .author-category a { font-weight: 500; } .home-blog-post:hover .overlay { opacity: 1; } .home-blog-post:hover .overlay a { -webkit-transform: none; transform: none; opacity: 1; } #blog-listing-big .post, #blog-listing-medium .post { margin-bottom: 60px; } #blog-listing-big .image, #blog-listing-medium .image { margin-bottom: 15px; } #blog-listing-big h2 a, #blog-listing-medium h2 a { color: #555; text-transform: uppercase; letter-spacing: 0.1em; } #blog-listing-big .author-category, #blog-listing-medium .author-category { color: #999; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 300; margin-bottom: 10px; font-size: .9rem; } #blog-listing-big .author-category a, #blog-listing-medium .author-category a { font-weight: 500; margin-bottom: 0; } #blog-listing-big .date-comments, #blog-listing-medium .date-comments { margin-bottom: 0; } #blog-listing-big .date-comments a, #blog-listing-medium .date-comments a { color: #999; margin-left: 25px; } #blog-listing-big .date-comments i, #blog-listing-medium .date-comments i { margin-right: 5px; } #blog-listing-big p.intro, #blog-listing-medium p.intro { margin-top: 10px; color: #777; } .pager { padding: 20px 0; margin-top: 20px; border-top: 1px solid #e6e6e6; } .disabled a, .disabled a:hover { border-color: #999 !important; color: #999 !important; background: none !important; cursor: not-allowed; } #blog-post #post-content { margin-bottom: 20px; } #blog-post .comment { margin-bottom: 25px; } #blog-post .comment .posted { color: #999; font-size: 0.8rem; margin-bottom: 10px; } #blog-post .comment.last { margin-bottom: 0; } #blog-post .comment p { font-size: 0.9rem; } #blog-post #comments, #blog-post #comment-form { padding: 20px 0; margin-top: 20px; border-top: solid 1px #eee; } #blog-post #comments h4, #blog-post #comment-form h4 { margin-bottom: 1.5rem; } #blog-post #comment-form { margin-bottom: 20px; } #blog-post .reply i { margin-right: 5px; } .box-simple { margin-bottom: 40px; text-align: center; } .box-simple:hover .icon-outlined { -webkit-transform: scale(1.1); transform: scale(1.1); } .box-simple .icon-outlined { color: #4fbfa8; border: 1px solid #4fbfa8; -webkit-transition: all 0.3s; transition: all 0.3s; } .box-simple h3 { text-transform: uppercase; letter-spacing: 0.1em; } .box-simple p { color: #999; margin: 20px 0; font-size: 0.9rem; } .box-simple.box-white { border: dotted 1px #999; padding: 15px 20px; margin-bottom: 0; } .box-simple.box-white .icon { font-size: 2rem; margin-bottom: 1rem; } .box-simple.box-white p { color: #999; margin-bottom: 5px; } .box-simple.box-dark { border: dotted 1px #999; padding: 15px 20px; margin-bottom: 0; color: #fff; background: #555; } .box-simple.box-dark .icon { font-size: 2rem; margin-bottom: 1rem; } .box-simple.box-dark p { margin-bottom: 5px; color: #fff; } .same-height div[class*="col-"] { margin-bottom: 30px; } .same-height .box-white, .same-height.box-dark { height: 100%; } .box-image { position: relative; margin: 20px 0; } .box-image:hover .overlay { opacity: 1; } .box-image:hover h3, .box-image:hover p, .box-image:hover .buttons { opacity: 1; -webkit-transform: none; transform: none; } .box-image:hover p { -webkit-transition-delay: 0.1s; transition-delay: 0.1s; } .box-image:hover .buttons { -webkit-transition-delay: 0.2s; transition-delay: 0.2s; } .box-image .name { margin-bottom: 20px; } .box-image a { text-decoration: none !important; } .box-image h3 { font-size: 1.125rem; text-transform: uppercase; letter-spacing: 0.1em; -webkit-transition: all 0.3s; transition: all 0.3s; opacity: 0; -webkit-transform: translateY(-50px); transform: translateY(-50px); } .box-image .overlay { width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: rgba(79, 191, 168, 0.6); padding: 20px; -webkit-transition: all 0.3s; transition: all 0.3s; opacity: 0; } .box-image .buttons { opacity: 0; -webkit-transition: all 0.3s; transition: all 0.3s; -webkit-transform: translateY(20px); transform: translateY(20px); } .box-image .buttons a { margin: 0 3px; } .box-image p { -webkit-transition: all 0.3s; transition: all 0.3s; opacity: 0; -webkit-transform: translateY(20px); transform: translateY(20px); } .box-image-text .content { text-align: center; } .box-image-text p { color: #777; font-size: 0.9rem; } .see-more { margin-top: 40px; } .see-more p { font-size: 1.8rem; font-weight: 100; } .box { margin: 80px 0; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; padding: 20px; } .box a.btn { margin-top: 25px; } .box .box-header { background: #f7f7f7; margin: 20px 0 20px; padding: 20px; border-top: 1px solid #eee; text-transform: uppercase; letter-spacing: 0.1em; } .box .box-footer { background: #f7f7f7; margin-top: 30px; padding: 20px; border-top: 1px solid #eee; } .box.shipping-method, .box.payment-method { margin: 0; margin-bottom: 20px; } .box.shipping-method h4, .box.payment-method h4 { text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 5px; } .box.shipping-method p, .box.payment-method p { font-size: 0.9rem; color: #555; } .box-image-text { margin: 15px 0; } .box-image-text .image { position: relative; margin-bottom: 15px; } .box-image-text .overlay { width: 100%; height: 100%; background: rgba(79, 191, 168, 0.6); position: absolute; top: 0; opacity: 0; -webkit-transition: all 0.5s; transition: all 0.5s; } .box-image-text .overlay a { -webkit-transform: translateY(30px); transform: translateY(30px); opacity: 0; } .box-image-text .overlay i { margin-right: 5px; } .box-image-text .text { text-align: center; } .box-image-text .text .intro { text-align: left; margin-bottom: 20px; color: #888; font-size: 0.9rem; } .box-image-text h4 { margin: 10px 0; text-transform: uppercase; letter-spacing: 0.1em; } .box-image-text .author-category { font-size: 0.75rem; color: #999; text-transform: uppercase; letter-spacing: 0.1em; } .box-image-text .author-category a { font-weight: 500; } .box-image-text:hover .overlay { opacity: 1; } .box-image-text:hover .overlay a { -webkit-transform: none; transform: none; opacity: 1; } .box-footer a { margin: 0 !important; } .box-footer .right-col i { margin-left: 5px; } .box-footer .left-col i { margin-right: 5px; } @media (max-width: 767px) { .box-footer { -webkit-box-pack: center !important; -ms-flex-pack: center !important; justify-content: center !important; } .box-footer a { margin-bottom: 10px !important; } } .jumbotron h1, .jumbotron h2 { letter-spacing: 0.1em; } .jumbotron p { letter-spacing: 0.1em; font-size: 1.3rem; } .package { text-align: center; border: 1px solid #4fbfa8; margin-top: 25px; margin-bottom: 20px; padding-bottom: 15px; overflow: hidden; } .package .package-header { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; height: 55px; } .package .package-header h5 { margin: 0; text-transform: uppercase; letter-spacing: 0.1em; display: inline-block; color: #555; } .package .package-header .meta-text { font-size: 0.8rem; } .package h4 { margin-top: 1rem; margin-bottom: 0; color: #333; } .package .price-container { margin-bottom: 20px; } .package .price-container span.period { color: #777; font-size: 0.9rem; margin-left: 10px; } .package ul { padding: 0 30px; } .package ul li { padding: 10px; border-bottom: 1px solid #eee; color: #555; font-size: 0.9rem; } .package ul i { margin-right: 10px; } .best-value .package { margin-top: 0; padding-bottom: 40px; border: 1px solid #4fbfa8; } .best-value .package .package-header { height: 85px; } .best-value .package .package-header h5 { color: #fff; } .team-member { margin-bottom: 40px; } .team-member .image { margin-bottom: 1rem; } .team-member a { color: #333; } .team-member a:hover { color: #333; } .team-member h3 { font-size: 1.125rem; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 5px; } .team-member p.role { font-size: 0.8rem; text-transform: uppercase; color: #999; margin-bottom: 0; } .team-member p { color: #999; font-size: 0.8rem; } .team-member-detail p:not(.lead) { color: #555; font-size: 0.9rem; } .portfolio p { color: #fff; } .portfolio-project p { font-size: 0.9rem; color: #888; } .portfolio-project .project-more p { padding: 5px 0; font-size: 0.85rem; } .portfolio-project .project-more h4 { margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.88rem; color: #555; } .portfolio-showcase { margin-bottom: 60px; } .portfolio-showcase h3 { text-transform: uppercase; -webkit-transition-property: 0.1rem; transition-property: 0.1rem; } .portfolio-showcase .buttons { margin-top: 40px; } .portfolio-showcase p:not(.lead) { color: #999; font-size: 0.9rem; } .portfolio-showcase p.lead { color: #666; } .product { text-align: center; position: relative; margin-bottom: 40px; } .product .image { overflow: hidden; max-height: 190px; } .product .image a { margin: 0; } .product .text { padding: 10px; border-bottom: 1px solid #ddd; } .product .text h3 { min-height: 63px; } .product .price { font-size: 1.1rem; } .product .price del { color: #999; display: inline-block; margin-right: 5px; } .product img { -webkit-transition: all 0.3s; transition: all 0.3s; } .product:hover img { -webkit-transform: scale(1.1); transform: scale(1.1); } .product:hover .text { border-color: #555; } .product a { text-transform: uppercase; letter-spacing: 0.1em; color: #333; text-decoration: none !important; margin-top: 15px; } .products-big .image { min-height: 250px; max-height: 250px; } .goToDescription { text-align: center; font-size: 0.8rem; margin-bottom: 40px; } .goToDescription a { color: #999; text-decoration: underline; } table tr td { vertical-align: middle !important; } #customer-orders a.btn { margin-top: 0; } #customer-orders table { font-size: 0.9rem; } #customer-orders th { border-top: 0; } #customer-order img { width: 50px; } #customer-order tfoot th { font-size: 1.2rem; font-weight: 300; } #customer-order p { font-size: 1.2rem; font-weight: 300; } #productMain #thumbs { margin-top: 30px; } #productMain input { display: none; } #productMain .sizes { text-align: center; } #productMain .sizes a { text-decoration: none !important; } #productMain .sizes a:hover { background: #4fbfa8; color: #fff; } #productMain .price { text-align: center; font-size: 2.5rem; margin: 30px 0; color: #555; } #basket thead th { border-top: none; } #basket tfoot th { font-size: 1.25rem; color: #555; } #order-summary tr td { color: #999; } #order-summary tr.total { font-size: 1.25rem; color: #555; font-weight: 700; } #order-summary tr.total td { color: #555; } #checkout .nav-pills { margin-bottom: 30px; border-bottom: 1px solid #4fbfa8; } #checkout .nav-pills a { font-size: 0.9rem; border-bottom: none !important; } #checkout .nav-pills a.disabled { cursor: not-allowed; } #checkout form label { margin-bottom: 5px; } #checkout .payment-method:hover, #checkout .shipping-method:hover { cursor: pointer; } @media (max-width: 767px) { .nav-pills { -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; } } .sidebar-menu .badge { font-weight: 700; margin: 0; } .sidebar-menu.with-icons a.nav-link { position: relative; } .sidebar-menu.with-icons a.nav-link::before { content: '\f105'; display: inline-block; position: absolute; top: 50%; right: 15px; -webkit-transform: translateY(-50%); transform: translateY(-50%); font-family: 'FontAwesome'; } .panel-heading { margin-bottom: 10px; } .panel-heading h3 { margin-top: 5px; } .panel-heading .btn { color: #fff; text-decoration: none !important; } .panel-heading .btn i { margin-right: 7px; } .panel-body p { font-size: .9rem; color: #555; } .panel-body label { font-size: 0.8rem; font-weight: 400 !important; color: #777; opacity: 0.9; display: inline-block; cursor: pointer; } .panel-body label input { margin-right: 5px; } .panel-body label:hover { opacity: 1; } .panel-body span.colour { display: inline-block; width: 14px; height: 14px; margin-top: 10px; margin-left: 5px; border: 1px solid #555; } .panel-body span.colour.white { background: #fff; } .panel-body span.colour.blue { background: #007bff; } .panel-body span.colour.green { background: #28a745; } .panel-body span.colour.red { background: #dc3545; } .panel-body span.colour.yellow { background: #ffc107; } .nav-pills .nav-link { border-radius: 0; } .nav-pills .nav-link:hover { background: #eee; } .nav-pills .nav-link.active { background: #4fbfa8; } .panel { margin-bottom: 20px; } .category-menu a.nav-link { text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; } ul ul a.nav-link { padding-left: 30px !important; font-size: 0.85rem; text-transform: none !important; font-weight: 400 !important; color: #777; } .tag-cloud a { font-size: 0.8rem; border: 1px solid #eee; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; padding: 5px 10px; text-transform: uppercase; margin: 3px 0; text-decoration: none !important; } .tag-cloud a:hover { border-color: #4fbfa8; } .tag-cloud i { margin-right: 5px; } .ribbon { width: 80px; height: 32px; line-height: 32px; background: #4fbfa8; color: #fff; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; font-size: 0.9rem; margin-bottom: 20px; position: absolute; top: 30px; left: -15px; text-align: center; } .ribbon.sale { background: #4fbfa8; } .ribbon.new { background: #5bc0de; } .ribbon.new::after { content: ''; border-top: 15px solid #1f7e9a; } .ribbon.sold { background: #f0ad4e; } .ribbon.sold::after { content: ''; border-top: 15px solid #b06d0f; } .ribbon.gift { background: #5cb85c; } .ribbon.gift::after { content: ''; border-top: 15px solid #2d672d; } .ribbon:nth-of-type(2) { top: 82px; } .ribbon::after { content: ''; border-top: 15px solid #308372; border-left: 15px solid transparent; border-right: 0 solid transparent; position: absolute; bottom: -15px; left: 0; display: block; } .badge { font-weight: 400; text-transform: uppercase; letter-spacing: 0.1em; padding: 3px 5px; } #map { border-top: 1px solid #4fbfa8; border-bottom: 1px solid #4fbfa8; height: 300px; } .get-it { background: #4fbfa8; color: #fff; padding: 50px 0; } .get-it h3 { text-transform: uppercase; letter-spacing: 0.1em; margin: 0; } footer.main-footer { padding: 60px 0; padding-bottom: 0; background: #555; color: #fff; } footer.main-footer a { color: inherit; color: #eee; } footer.main-footer h4 { color: #eee; margin: 10px 0; text-transform: uppercase; letter-spacing: 0.1em; } footer.main-footer p { font-size: 0.9rem; color: #aaa; } footer.main-footer hr { border: none; border-top: 1px solid #ddd; background: none; } footer.main-footer .footer-blog-list li { margin-bottom: 20px; } footer.main-footer .image { width: 40px; height: 40px; margin-right: 10px; -ms-flex-negative: 0; flex-shrink: 0; } footer.main-footer .text { text-transform: uppercase; letter-spacing: 0.1em; } footer.main-footer .text a { font-size: 0.8rem; } footer.main-footer .photo-stream li { margin: 0; } footer.main-footer .photo-stream a { width: 80px; height: 80px; padding: 5px; display: block; } footer.main-footer .copyrights { padding: 50px 0; background: #333; color: #ccc; margin-top: 50px; } footer.main-footer .copyrights a { color: #4fbfa8; } footer.main-footer .copyrights p { color: inherit; font-size: 0.8rem; margin-bottom: 0; } @media (max-width: 991px) { footer.main-footer .photo-stream a { width: 120px; height: 120px; } } @media (max-width: 767px) { footer.main-footer .photo-stream li { width: 32%; margin-bottom: 10px; } footer.main-footer .photo-stream a { width: 100%; height: auto; } } /* ===================== STYLE SWITCHER FOR DEMO ===================== */ #style-switch-button { position: fixed; top: 120px; left: 0px; border-radius: 0; z-index: 2; } #style-switch { width: 300px; padding: 20px; position: fixed; top: 160px; left: 0; background: #fff; border: solid 1px #ced4da; z-index: 2000; } #style-switch h4 { color: #495057; } /* * 1. NAVBAR */ .navbar { padding: 0.5rem 1rem; } .navbar-brand { display: inline-block; padding-top: 0.3125rem; padding-bottom: 0.3125rem; margin-right: 1rem; font-size: 1.25rem; } .navbar-toggler { padding: 0.25rem 0.75rem; font-size: 1.25rem; line-height: 1; border: 1px solid transparent; border-radius: 0; } .navbar-light .navbar-brand { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-nav .nav-link { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { color: rgba(0, 0, 0, 0.7); } .navbar-light .navbar-nav .nav-link.disabled { color: rgba(0, 0, 0, 0.3); } .navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .nav-link.active { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-toggler { color: rgba(0, 0, 0, 0.5); border-color: rgba(0, 0, 0, 0.1); } .navbar-light .navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } .navbar-light .navbar-text { color: rgba(0, 0, 0, 0.5); } .navbar-dark .navbar-brand { color: #fff; } .navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { color: #fff; } .navbar-dark .navbar-nav .nav-link { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { color: rgba(255, 255, 255, 0.75); } .navbar-dark .navbar-nav .nav-link.disabled { color: rgba(255, 255, 255, 0.25); } .navbar-dark .navbar-nav .show > .nav-link, .navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .nav-link.active { color: #fff; } .navbar-dark .navbar-toggler { color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.1); } .navbar-dark .navbar-toggler-icon { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } .navbar-dark .navbar-text { color: rgba(255, 255, 255, 0.5); } /* * 2. BUTTONS */ .btn { font-weight: 700; border: 1px solid transparent; padding: 0.5rem 0.75rem; font-size: 0.8rem; line-height: 1.5; border-radius: 0; -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; } .btn:focus, .btn.focus { outline: 0; -webkit-box-shadow: 0 0 0 0.2rem rgba(79, 191, 168, 0.25); box-shadow: 0 0 0 0.2rem rgba(79, 191, 168, 0.25); } .btn.disabled, .btn:disabled { opacity: .65; } .btn:not([disabled]):not(.disabled):active, .btn:not([disabled]):not(.disabled).active { background-image: none; } .btn-primary { color: #111; background-color: #4fbfa8; border-color: #4fbfa8; } .btn-primary:hover { color: #111; background-color: #3eaa94; border-color: #3aa18c; } .btn-primary:focus, .btn-primary.focus { -webkit-box-shadow: 0 0 0 3px rgba(79, 191, 168, 0.5); box-shadow: 0 0 0 3px rgba(79, 191, 168, 0.5); } .btn-primary.disabled, .btn-primary:disabled { background-color: #4fbfa8; border-color: #4fbfa8; } .btn-primary:active, .btn-primary.active, .show > .btn-primary.dropdown-toggle { background-color: #3eaa94; background-image: none; border-color: #3aa18c; } .btn-secondary { color: #fff; background-color: #868e96; border-color: #868e96; } .btn-secondary:hover { color: #fff; background-color: #727b84; border-color: #6c757d; } .btn-secondary:focus, .btn-secondary.focus { -webkit-box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); } .btn-secondary.disabled, .btn-secondary:disabled { background-color: #868e96; border-color: #868e96; } .btn-secondary:active, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle { background-color: #727b84; background-image: none; border-color: #6c757d; } .btn-success { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-success:hover { color: #fff; background-color: #218838; border-color: #1e7e34; } .btn-success:focus, .btn-success.focus { -webkit-box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); } .btn-success.disabled, .btn-success:disabled { background-color: #28a745; border-color: #28a745; } .btn-success:active, .btn-success.active, .show > .btn-success.dropdown-toggle { background-color: #218838; background-image: none; border-color: #1e7e34; } .btn-info { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-info:hover { color: #fff; background-color: #138496; border-color: #117a8b; } .btn-info:focus, .btn-info.focus { -webkit-box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); } .btn-info.disabled, .btn-info:disabled { background-color: #17a2b8; border-color: #17a2b8; } .btn-info:active, .btn-info.active, .show > .btn-info.dropdown-toggle { background-color: #138496; background-image: none; border-color: #117a8b; } .btn-warning { color: #111; background-color: #ffc107; border-color: #ffc107; } .btn-warning:hover { color: #111; background-color: #e0a800; border-color: #d39e00; } .btn-warning:focus, .btn-warning.focus { -webkit-box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); } .btn-warning.disabled, .btn-warning:disabled { background-color: #ffc107; border-color: #ffc107; } .btn-warning:active, .btn-warning.active, .show > .btn-warning.dropdown-toggle { background-color: #e0a800; background-image: none; border-color: #d39e00; } .btn-danger { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-danger:hover { color: #fff; background-color: #c82333; border-color: #bd2130; } .btn-danger:focus, .btn-danger.focus { -webkit-box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); } .btn-danger.disabled, .btn-danger:disabled { background-color: #dc3545; border-color: #dc3545; } .btn-danger:active, .btn-danger.active, .show > .btn-danger.dropdown-toggle { background-color: #c82333; background-image: none; border-color: #bd2130; } .btn-light { color: #111; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:hover { color: #111; background-color: #e2e6ea; border-color: #dae0e5; } .btn-light:focus, .btn-light.focus { -webkit-box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); } .btn-light.disabled, .btn-light:disabled { background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:active, .btn-light.active, .show > .btn-light.dropdown-toggle { background-color: #e2e6ea; background-image: none; border-color: #dae0e5; } .btn-dark { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-dark:hover { color: #fff; background-color: #23272b; border-color: #1d2124; } .btn-dark:focus, .btn-dark.focus { -webkit-box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); } .btn-dark.disabled, .btn-dark:disabled { background-color: #343a40; border-color: #343a40; } .btn-dark:active, .btn-dark.active, .show > .btn-dark.dropdown-toggle { background-color: #23272b; background-image: none; border-color: #1d2124; } .btn-default { color: #111; background-color: #ced4da; border-color: #ced4da; } .btn-default:hover { color: #111; background-color: #b8c1ca; border-color: #b1bbc4; } .btn-default:focus, .btn-default.focus { -webkit-box-shadow: 0 0 0 3px rgba(206, 212, 218, 0.5); box-shadow: 0 0 0 3px rgba(206, 212, 218, 0.5); } .btn-default.disabled, .btn-default:disabled { background-color: #ced4da; border-color: #ced4da; } .btn-default:active, .btn-default.active, .show > .btn-default.dropdown-toggle { background-color: #b8c1ca; background-image: none; border-color: #b1bbc4; } .btn-outline-primary { color: #4fbfa8; background-color: transparent; background-image: none; border-color: #4fbfa8; } .btn-outline-primary:hover { color: #fff; background-color: #4fbfa8; border-color: #4fbfa8; } .btn-outline-primary:focus, .btn-outline-primary.focus { -webkit-box-shadow: 0 0 0 3px rgba(79, 191, 168, 0.5); box-shadow: 0 0 0 3px rgba(79, 191, 168, 0.5); } .btn-outline-primary.disabled, .btn-outline-primary:disabled { color: #4fbfa8; background-color: transparent; } .btn-outline-primary:active, .btn-outline-primary.active, .show > .btn-outline-primary.dropdown-toggle { color: #fff; background-color: #4fbfa8; border-color: #4fbfa8; } .btn-outline-secondary { color: #868e96; background-color: transparent; background-image: none; border-color: #868e96; } .btn-outline-secondary:hover { color: #fff; background-color: #868e96; border-color: #868e96; } .btn-outline-secondary:focus, .btn-outline-secondary.focus { -webkit-box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); } .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { color: #868e96; background-color: transparent; } .btn-outline-secondary:active, .btn-outline-secondary.active, .show > .btn-outline-secondary.dropdown-toggle { color: #fff; background-color: #868e96; border-color: #868e96; } .btn-outline-success { color: #28a745; background-color: transparent; background-image: none; border-color: #28a745; } .btn-outline-success:hover { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:focus, .btn-outline-success.focus { -webkit-box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); } .btn-outline-success.disabled, .btn-outline-success:disabled { color: #28a745; background-color: transparent; } .btn-outline-success:active, .btn-outline-success.active, .show > .btn-outline-success.dropdown-toggle { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-info { color: #17a2b8; background-color: transparent; background-image: none; border-color: #17a2b8; } .btn-outline-info:hover { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:focus, .btn-outline-info.focus { -webkit-box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); } .btn-outline-info.disabled, .btn-outline-info:disabled { color: #17a2b8; background-color: transparent; } .btn-outline-info:active, .btn-outline-info.active, .show > .btn-outline-info.dropdown-toggle { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-warning { color: #ffc107; background-color: transparent; background-image: none; border-color: #ffc107; } .btn-outline-warning:hover { color: #fff; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:focus, .btn-outline-warning.focus { -webkit-box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); } .btn-outline-warning.disabled, .btn-outline-warning:disabled { color: #ffc107; background-color: transparent; } .btn-outline-warning:active, .btn-outline-warning.active, .show > .btn-outline-warning.dropdown-toggle { color: #fff; background-color: #ffc107; border-color: #ffc107; } .btn-outline-danger { color: #dc3545; background-color: transparent; background-image: none; border-color: #dc3545; } .btn-outline-danger:hover { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:focus, .btn-outline-danger.focus { -webkit-box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); } .btn-outline-danger.disabled, .btn-outline-danger:disabled { color: #dc3545; background-color: transparent; } .btn-outline-danger:active, .btn-outline-danger.active, .show > .btn-outline-danger.dropdown-toggle { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-light { color: #f8f9fa; background-color: transparent; background-image: none; border-color: #f8f9fa; } .btn-outline-light:hover { color: #fff; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:focus, .btn-outline-light.focus { -webkit-box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); } .btn-outline-light.disabled, .btn-outline-light:disabled { color: #f8f9fa; background-color: transparent; } .btn-outline-light:active, .btn-outline-light.active, .show > .btn-outline-light.dropdown-toggle { color: #fff; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-dark { color: #343a40; background-color: transparent; background-image: none; border-color: #343a40; } .btn-outline-dark:hover { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:focus, .btn-outline-dark.focus { -webkit-box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); } .btn-outline-dark.disabled, .btn-outline-dark:disabled { color: #343a40; background-color: transparent; } .btn-outline-dark:active, .btn-outline-dark.active, .show > .btn-outline-dark.dropdown-toggle { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-lg { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0; } .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.7rem; line-height: 1.5; border-radius: 0; } /* * 3. TYPE */ body { font-family: "Roboto", Helvetica, Arial, sans-serif; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; background-color: #fff; } a { color: #4fbfa8; text-decoration: none; } a:focus, a:hover { color: #348e7b; text-decoration: underline; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-bottom: 1rem; font-family: inherit; font-weight: 700; line-height: 1.1; color: inherit; } h1, .h1 { font-size: 2.2rem; } h2, .h2 { font-size: 1.8rem; } h3, .h3 { font-size: 1.5rem; } h4, .h4 { font-size: 1.1rem; } h5, .h5 { font-size: 1rem; } h6, .h6 { font-size: 0.8rem; } .lead { font-size: 1.25rem; font-weight: 300; } .display-1 { font-size: 6rem; font-weight: 300; line-height: 1.1; } .display-2 { font-size: 5.5rem; font-weight: 300; line-height: 1.1; } .display-3 { font-size: 4.5rem; font-weight: 300; line-height: 1.1; } .display-4 { font-size: 3.5rem; font-weight: 300; line-height: 1.1; } hr { border-top: 1px solid rgba(0, 0, 0, 0.1); } small, .small { font-size: 80%; font-weight: 400; } mark, .mark { padding: 0.2em; background-color: #fcf8e3; } .blockquote { padding: 0.5rem 1rem; margin-bottom: 2rem; font-size: 1rem; border-left: 5px solid #4fbfa8; } .blockquote-footer { color: #868e96; } .blockquote-footer::before { content: "\2014 \00A0"; } .text-primary { color: #4fbfa8 !important; } a.text-primary:focus, a.text-primary:hover { color: #3aa18c !important; } /* * 4. PAGINATION */ .page-item:first-child .page-link { border-top-left-radius: 0; border-bottom-left-radius: 0; } .page-item:last-child .page-link { border-top-right-radius: 0; border-bottom-right-radius: 0; } .page-item.active .page-link { color: #fff; background-color: #4fbfa8; border-color: #4fbfa8; } .page-item.disabled .page-link { color: #868e96; background-color: #fff; border-color: #dee2e6; } .page-link { padding: 0.5rem 0.75rem; line-height: 1.25; color: #4fbfa8; background-color: #fff; border: 1px solid #dee2e6; } .page-link:focus, .page-link:hover { color: #348e7b; text-decoration: none; background-color: #e9ecef; border-color: #dee2e6; outline: 0; } .pagination-lg .page-link { padding: 0.75rem 1.5rem; font-size: 1.25rem; line-height: 1.5; } .pagination-lg .page-item:first-child .page-link { border-top-left-radius: 0; border-bottom-left-radius: 0; } .pagination-lg .page-item:last-child .page-link { border-top-right-radius: 0; border-bottom-right-radius: 0; } .pagination-sm .page-link { padding: 0.25rem 0.5rem; font-size: 0.7rem; line-height: 1.5; } .pagination-sm .page-item:first-child .page-link { border-top-left-radius: 0; border-bottom-left-radius: 0; } .pagination-sm .page-item:last-child .page-link { border-top-right-radius: 0; border-bottom-right-radius: 0; } /* * 5. DROPDOWNS */ .dropdown-menu { z-index: 1000; min-width: 10rem; padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 1rem; color: #212529; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 0; } .dropdown-item { padding: 0.25rem 1.5rem; color: #212529; } .dropdown-item:focus, .dropdown-item:hover { color: #16181b; background-color: #f8f9fa; } .dropdown-item.active, .dropdown-item:active { color: #fff; background-color: #4fbfa8; } .dropdown-item.disabled, .dropdown-item:disabled { color: #868e96; } .dropdown-header { padding: 0.5rem 1.5rem; font-size: 0.7rem; color: #868e96; } /* * 6. UTILITIES */ .bg-primary { background-color: #4fbfa8 !important; } a.bg-primary:focus, a.bg-primary:hover { background-color: #3aa18c !important; } .bg-secondary { background-color: #868e96 !important; } a.bg-secondary:focus, a.bg-secondary:hover { background-color: #6c757d !important; } .bg-success { background-color: #28a745 !important; } a.bg-success:focus, a.bg-success:hover { background-color: #1e7e34 !important; } .bg-info { background-color: #17a2b8 !important; } a.bg-info:focus, a.bg-info:hover { background-color: #117a8b !important; } .bg-warning { background-color: #ffc107 !important; } a.bg-warning:focus, a.bg-warning:hover { background-color: #d39e00 !important; } .bg-danger { background-color: #dc3545 !important; } a.bg-danger:focus, a.bg-danger:hover { background-color: #bd2130 !important; } .bg-light { background-color: #f8f9fa !important; } a.bg-light:focus, a.bg-light:hover { background-color: #dae0e5 !important; } .bg-dark { background-color: #343a40 !important; } a.bg-dark:focus, a.bg-dark:hover { background-color: #1d2124 !important; } .border-primary { border-color: #4fbfa8 !important; } .border-secondary { border-color: #868e96 !important; } .border-success { border-color: #28a745 !important; } .border-info { border-color: #17a2b8 !important; } .border-warning { border-color: #ffc107 !important; } .border-danger { border-color: #dc3545 !important; } .border-light { border-color: #f8f9fa !important; } .border-dark { border-color: #343a40 !important; } .text-primary { color: #4fbfa8 !important; } a.text-primary:focus, a.text-primary:hover { color: #3aa18c !important; } .text-secondary { color: #868e96 !important; } a.text-secondary:focus, a.text-secondary:hover { color: #6c757d !important; } .text-success { color: #28a745 !important; } a.text-success:focus, a.text-success:hover { color: #1e7e34 !important; } .text-info { color: #17a2b8 !important; } a.text-info:focus, a.text-info:hover { color: #117a8b !important; } .text-warning { color: #ffc107 !important; } a.text-warning:focus, a.text-warning:hover { color: #d39e00 !important; } .text-danger { color: #dc3545 !important; } a.text-danger:focus, a.text-danger:hover { color: #bd2130 !important; } .text-light { color: #f8f9fa !important; } a.text-light:focus, a.text-light:hover { color: #dae0e5 !important; } .text-dark { color: #343a40 !important; } a.text-dark:focus, a.text-dark:hover { color: #1d2124 !important; } .badge-primary { color: #111; background-color: #4fbfa8; } .badge-primary[href]:focus, .badge-primary[href]:hover { color: #111; text-decoration: none; background-color: #3aa18c; } .badge-secondary { color: #fff; background-color: #868e96; } .badge-secondary[href]:focus, .badge-secondary[href]:hover { color: #fff; text-decoration: none; background-color: #6c757d; } .badge-success { color: #fff; background-color: #28a745; } .badge-success[href]:focus, .badge-success[href]:hover { color: #fff; text-decoration: none; background-color: #1e7e34; } .badge-info { color: #fff; background-color: #17a2b8; } .badge-info[href]:focus, .badge-info[href]:hover { color: #fff; text-decoration: none; background-color: #117a8b; } .badge-warning { color: #111; background-color: #ffc107; } .badge-warning[href]:focus, .badge-warning[href]:hover { color: #111; text-decoration: none; background-color: #d39e00; } .badge-danger { color: #fff; background-color: #dc3545; } .badge-danger[href]:focus, .badge-danger[href]:hover { color: #fff; text-decoration: none; background-color: #bd2130; } .badge-light { color: #111; background-color: #f8f9fa; } .badge-light[href]:focus, .badge-light[href]:hover { color: #111; text-decoration: none; background-color: #dae0e5; } .badge-dark { color: #fff; background-color: #343a40; } .badge-dark[href]:focus, .badge-dark[href]:hover { color: #fff; text-decoration: none; background-color: #1d2124; } /* * 7.CODE */ code { font-size: 87.5%; color: #e83e8c; border-radius: 0; } a > code { padding: 0; color: inherit; background-color: inherit; } /* * 8. NAV */ .nav-link { padding: 0.5rem 1rem; } .nav-link.disabled { color: #868e96; } .nav-tabs .nav-item { margin-bottom: -1px; } .nav-tabs .nav-link { border: 1px solid transparent; border-top-left-radius: 0; border-top-right-radius: 0; } .nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { border-color: #e9ecef #e9ecef #dee2e6; } .nav-tabs .nav-link.disabled { color: #868e96; } .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { color: #495057; background-color: #fff; } .nav-tabs .dropdown-menu { margin-top: -1px; } .nav-pills .nav-link { border-radius: 0; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { color: #fff; background-color: #4fbfa8; } /* * 9. CARD */ .card { background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0; } .card > .list-group:first-child .list-group-item:first-child { border-top-left-radius: 0; border-top-right-radius: 0; } .card > .list-group:last-child .list-group-item:last-child { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .card-body { padding: 1.25rem; } .card-title { margin-bottom: 0.75rem; } .card-subtitle { margin-top: -0.375rem; } .card-link + .card-link { margin-left: 1.25rem; } .card-header { padding: 0.75rem 1.25rem; background-color: rgba(0, 0, 0, 0.03); border-bottom: 1px solid rgba(0, 0, 0, 0.125); } .card-header:first-child { border-radius: calc(0 - 1px) calc(0 - 1px) 0 0; } .card-header-transparent { background-color: rgba(0, 0, 0, 0.3); border-bottom: none; } .card-footer { padding: 0.75rem 1.25rem; background-color: #f8f9fa; border-top: 1px solid rgba(0, 0, 0, 0.125); } .card-footer:last-child { border-radius: 0 0 calc(0 - 1px) calc(0 - 1px); } .card-header-tabs { margin-right: -0.625rem; margin-bottom: -0.75rem; margin-left: -0.625rem; border-bottom: 0; } .card-header-pills { margin-right: -0.625rem; margin-left: -0.625rem; } .card-img-overlay { padding: 1.25rem; } .card-img-overlay-opacity { background: rgba(0, 0, 0, 0.2); } .card-img { border-radius: calc(0 - 1px); } .card-img-top { border-top-left-radius: calc(0 - 1px); border-top-right-radius: calc(0 - 1px); } .card-img-bottom { border-bottom-right-radius: calc(0 - 1px); border-bottom-left-radius: calc(0 - 1px); } .card-deck .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-deck { margin-right: -15px; margin-left: -15px; } .card-deck .card { margin-right: 15px; margin-left: 15px; } } doit-0.36.0/doc/_static/universal/texture-bw.png000066400000000000000000002373321423054503100215650ustar00rootroot00000000000000PNG  IHDRXX >IDATxڄ]j:?`ATƖff=gi&4f<ן}|޵r|uc|(Gvq~t%~鷯鲌/=AsܱƸkoVQ>WZo~{]'?{3|ֿ~}zP ^PLJz?|'?({i|z;4B;1{㙖?Cs$+ߺ=N[X]1YMU9d+|h[`[rAW<0;,xW^{x6EmO~Z`AnTwW*˧/Kk^|c / />Nm[B o+{^5ԋKNBz+; j,VP5\z}Ƕ*?K~~ ~3~jU5B8+ZE+PbٱU}xr]E=Wcj\l,Lcɹortgq,.qDH^vHep`>kdi*+z%O[ ["BMi l%ݱ+ⴶKnZ\r]w&ۿCOF)du2o[zLe"ʇۢt3e g1emSY79d!\KQx2v}JZ&&;,ȱam̺P%OE%k]m>z%כ[pڗa%-6uG"$^.Z2zag+.޽ʒH>&Zq7CZIVAO+ֿBw)iiٔ{^iw=Om~11oŘ TEHzuѾӋ7MwkrVuc{C}71OСcrx4 : kxrj3 :o{/WQ: :S X\٬Zkij7EƉqmH--P5w/n' +wu,[Ą.NAf8tJ@ϿUYWE]2ˑ4`Wj ڹ~@2ZD ֱjbB֊"ZBjQd2i!NWq2i ,FޱS?!(@k9 c,ΡMΑH=:&x?k(!njk cYF9 He!0HE#EfpUU{C){f,_ NJݠX#߅ q[``d*,lZ-0}ưP/ 솱x~p"8(p)uAkG!vT! (ZcsMabUD!#ac"@*gW^PhbMMƪzDvV ξ DE"+ G<xz2<%#y,"]E0nF4O:w}sh۳  Z[v_pz ` fV@*,A rt]d±%' ЅW5C>V<> KN6sT>qkK 1 v,]Gatuyu.Xa^]p6#K1,{(dhaKFfQ_\[rBzJid0TUW,״ 9`x}l\gc8NٱpB;X*H#qŃI \$QO<bɍDSz]ޱO= ,*EgH 0{=KXwuuo8Wֱ&l=g>"T)!1Fn,xb5ߟj&[$O,(ni24HЫ8.X4N8$,+N(bjqwmMͶ :s7P;ZmVzKT[F4,j;8Cu9Ɏv\Z%9Os@!UɎfLsLq@aʷq+ey"*Gct[]mY#$KI1SmEkN^}C Qp,՚N:kSY O4w ` od@1mܝ5zZ޿訳-l0H}lWص:C:+,Ya\XZ30¿ T+Az0Qsc~M8u!j &UzaXt(Y3__b'byj3lq)Wp9 #)ag|VH(9BR"b>=2p"|1H^@Rwiy>@^e26"j]+ѤnOkwjJzƿV!sd!1c1{ogbF`զ9X'F)"÷ 2][ WP6 ~7w%BwŸ"ۤm(B.l<_wSx8c so % IzSX3!op"4F8Q-2,7/HťҚDjW_F ?_{~4ޡsm`6IuOy2kO3`u&Pחxɍr"E"bze SUƔQ @9U~3WT԰9nt)܉#k&R(N3i-wۚKV^ D]H>P HDJF~b0 ˑތ_M:r 1ans!zH^6e ԪLS__ >DSͧF!QUErx<8;Y@JS߬7 (,h8Ԑاא@owSqɢ:Rw2>['Vf I]Z? ޠTgms2e]z;QS#PDT^]y]Hsm9аX2ƕ5{iG9Gcyl D_݀ eqeQuri'0X6vV ڷJ%,8BͷӨ?'{jjۮ>86NqQtG=a]K}`2(ʃ:%|^`h`;kęf쁝\a? ?~sYf}?TT0޸aqͥwQ&aaF‚b`KJP96=^_)Rq'𨂜7J,|ߥ՛ !yn,FE"MLu’_tp~Ҙ˝7z]r%Z{/%F&6cͷ;߭ ƓߥL˸(88ϳS5{C!M4~+ ՠ+"{ǐT& zg=F#zpa&_*eޮtbdFdeFJ'\@2#w KND4G_+I Sl'M;bBTAlU괳RLUkfgJ ,%tP MoŎ;Ujj<ԟ(Pcsӣkz9*DX&jm-dG{oJo&?YǣD% /2e.F~{ h%hMdrLZ&d•ĕM_&h 6{]K)w >/UK |\gkȁ[C4pTN 'zh-rnwqZeVxߪ~tJ=޸u[UǤzw4}߽E\6 TFM9Qt} Xt^yam6bX +kQ~dU9f:lĕ5izOijr^hJ?٘8ݾd=Ⱥl.Guזq"d>AtM^6 ^T5+aKx6v}߼pT/Dǫ i^ 4Z9E~S]5Z٤3l+Zup8܂J |0WϧΣu B[:m]^9J3VKZQQ.rHEQ]B`G};mn#QLC"FMl(]U"\iHZ6Yt ҄ހPď7ؖG/A#Af(El'I7E G2!Dx>5TAsMe638J4zDXIdq߀/(]d!{Il} clX7^|)DQ])۵qɆuZ (ښb5}Z~W~E @w3c[>1l_ty+[3 \bڛ= y0Ty}`wJ3,&yח3M]g{/Ý1 p!@=Fg6|Ϩgc9(yu#焦? =HeA2)yM˜"k8V"5:-SM[(#2p%83v`"933}bts$9\~9ʓ !Gk1Е3GD'i"P\ fC#ytNǴb)1"JrJ3ǂF|tIFL5!Qnyr>8Fm쯯kr#)b4Z.Zg}>eB"e3&! Tl(.׎O<<3)!>Y^B{<< yy|`ŗ~RrOrOgɛ3lM  9={B@Y(zcG^9inq62w$$ !TWY2glRЁ6n {E MmW@[Pޤ:]xR'<Έ6~x~ث:! e0޶:,؄6$5 2srъ]J~zSet"M\lW]֘S MAK7)Sߒ2EoApgبN@sl`DtSuҍikƝԚ9< Tg/NjwkHegqX :k>Y N\02HtU^. 7;b6CtVM:f{'NR)q.0M^\`ݵSD:lʚ3M8]ʝ8#qI ed%߉BI9wDX o#fT;ŀ2I^Ppi$r B+ip!)|ݾύ`2$88l-2Rc.ڽ|u%x; l 9q|,- l211s/Gv C=zA6Hc4+ZJ {+x6<9ҌO[74uQ*Ÿ08up7Z0Pa|/ͺۃR~bJSdZdžw5f X5spZfyʝ@Sz1ܵRӕī:>50C4Y[ixC1wЬXdEt**"b1`djWM:FHsAgpW`x~gaO( rdXs ic=nճðRb>;3"m[Fns%HKކsd~d,| 0WslG)BmVN]#K`/)h<^ZAnzc iޘ9J le)VJ' 0t&I"; sk s~xW4Hfx2}JյjlW?k2T? )U :MqR(fRh^?3^K雌)Qřr1}K xV#[O h{۾އ*Ts:Aȵ٥MwϬ#IttكD{o^m^k_Ú%)x TN8]wzS2Qͭwی7 [z{forW_– D( gԓŵ$P /`g #ׇO8xE& 4Rj:Q'oww>}t LjmgZևm{rɬ)Őj;Wd: s?ōK kpD'؅5(Odr,VB̈p]PYPfB#vLY{u2z*,Z̓jɳCMOTy#1!vf%wv[pU8|Ne&p 5 j&I3K,(X;Om$<kK߰`ٲKy$ ae|X^l1A4iE6(;d@Wr f"scopvc=>SOC) {mO>p".熐>rMLk ^|YP|2a EjϢ3BdXU94ݩ.# x7 XWK2csvXE-n >!%xjO\[_48!%d-nE zBnD߮zSC'CsNsK..u .H\z%'D Jiߧ`=93Ρp0%_ӒP|."G)`th[P*OMU%tZppV.ut{Xse6=[wb]X$(-fn4g-y+ ǁyiÍ&4*HcW-/_mEq݌CBngҏ8]~Vn5~ Uz)>NU]q#ݐ.kc7Ң8lxU )OJW>%>Q,t´[^rْ\S!Σ T}ggrIPG˭%WpqSe# EGGY= 0Pp%ZaN٤W0av5r􈽞E훾{ jڝgr_):g /r0<-']ݮ>͞U(5%-õJeJZ\=ɵ=<~$hSEՂIהYdHb8x՝5 G?ݐUZfɥ1{oP,EJ{e{49;I)Q4pd'S*{fIDPǁa:[|zw zi-hܛY޵Ԣ}Pi8ʅ߾,SŇ 2Hurq{j~&8ITSPpOT-p@ 2)= h4GYB8ljƺ~4LBe"##! 2+bkPuVh3cڼ$w'N3>`DlEspڅrd8#\2ڬ2e@1@YB,ZRSFSA@€9FRPybv_.*=!V=oAfjY.˔-ee}<ow=spd#i嬝/nh 4.Y9΅3nG<~UxR-S]S>@REu j>r(ft㢝Nh}#FmV=UfcqHd D-47,/p|A *|N j8WL,L`!hty2uKP6_gD"_t  ) 6ANH*n*.2bKc&/үPx3&UP^owCzBYDONlA^Y\ȰR-Ҷ[ sɬn~ǁQ&=Jz |_ңKr41dO6X~K}~\'<@s/ÒH9{Ms!J>Vr|/Q@2M,`w8@\D6Ow$(#m7r̐>6z {o[V> )oywGIZ@A~뿨?I:b Wv 7JrWխ:!8ŰϮz\zAXE hsy$㋚@[c2']Γ#rN`z52A ,Mw,Qy+bexcNH,pP} z3 H[! 5pH+-d1:b5qgϖYES [}>6XetŢJ)@h fsX:ܐie !C&? ?HxwN€2UWl2aU.’IȈ1SNV0&:/^ _傎tf0]RBo>&85`K0Sps,N|P78UF:SF-!'C>"^z,4,U`/9,|r}뱮OR'sjd2hJ\0E,i >g-[3{\.DK7b2 ad 3M? O(1VQ Պ{kY B}1#>-t":) Ox#= ȟ64&p_Ù0SAK*ٲ^DpXZlD#Xm[ uȀr84J--/OdҮbLk#0}k666>fvYoznGzGdB*< 6F/M䝠"ы ^Ci+W2p6=B<h5@ .""0[L;Fq|8zoόzD[*jYn1r7;gGr`zdґ9e BYx\I+2X{ԂB"k]aM &ŋXAp`UǗ5jvҰS+Hh=m;#G+YWŌ5׌k}ӒUu.~Ԯm.$ ߞʭCXn_R&pxQQdoC{eܙ V+[b"qmmqN`#d kd4};爲4E~#3#n${~Qߖw'^Zh%!ϩxfGdM:$꺝B]b!?lZTI*Wpf8:51;Q66s=J ʺ6عW[rPNu{{R1C*G)g#\\ }kf.̓p/2^P:Fp@iv܍Z2)導0qQWD qBX] k߶4 :STӢ{aH$%yVnϾ `V}ϕ1j^і3vs GSaK*9ׂTv?Y՛?A2t΄uM+rR4¸N#k0oln \$xܯڵด?]&Ma ~)Z\(]h\4|fdˑh7n7:XiU(WHeFk?I@L%f $ ʒb!4{2ݴM(y$TѮr"k7ufpzrSLg{Ά1K9_q@S_[**|F./HsC;?hY)DqV,^Xn~F<:0z#x~< 6{)ǣS)xr{[P XL!w'T"E1=΍z԰%.Zy[b,6)@n/A{*rUY,&i5IycP2%d/n-^VP.fIfB/ʡ c7 BhBH`raxTۊt7\5Apz]Z&+loùwW@Ȩ($)30c iH]C!;%1ھΈ&W|( 9:8v}e_ze؝ZǰVX/x}>l8Eyp_`b4/?f.i,i_ 29*cJ\ెJz'D#> F>3#=ςoYqjcayבk28ѱTRYU{ˏ.Az9oId '`.v(c7Gì_HN/]ɘĩ<[t}i~ e =N*4u&?кg|z,RMُl?Uks- XrM7+8oRgy=>At8]9Wv沲$w%DFG\KvdqZ/ jp#~7AS;4ѭ (-1DQ>H5;aQ|@MLD"Nt.DB:u}R̰V t찜i]p٭Ҫ)P]u\oA*K{fuPl缳PWJIfd͚dXHGCQ1zvH瞎<70Qf527ݛjVtq<HEJ♎Yo\K]ɽk`ts .=0T8KwĽ׳8[ WL  8@KF,⭤+JzT: ٛ`UZT[WRk ϴCUTx?ޥE&!.g,y+qjWhA2c.9Rg}_0&/ΛL\oxFKk1;ak豍5AEhyFh@Q90r ӳ7r<:.~7!|WV\VQ2e~,W739S5bt2f#mdxL㭦YZª\!tlɽ٬Z7ꊚp2,}z[.0׮dtDbNsN;Git8؀+]I,5'~ORZ.ɢ{ 9(aNiI5a%SkL=ˍ3@ϊWFvNGw^*tSbo4"ȴn(7dJ fbdlHO)́^12z xZ :~YA7xtoλK[&z Ac29_d&Tv K4 VkefnZ}y lUgdP@R6T{=O;WCR4`};H+) DQ Φ)j͞CY.1وxgE,#F?Ǝd+m&b_M4=mY8 [PZ(ݽ2.:ƂD &FrE%iu .'K)w Ltoͥ8|(^+ B3ˆnUNҌ.TYӆ<96 0ruy (B1f'sj%@۳2(̇' ftx Ayr9FܘBbIW r/*[ 1lnn*lxa Vڕ>q!_>7ev. 3􅕉 T` w)xލ6"uG6 w[Ȏi3nH5YhGZfOi9mF3UH'k$V5*Jq?WEJ66t`=p|op5{b ^]2ca?jԬ~ {as|@S7*wF0̥A- /^@9㤓y>dJ;8e bcTm߀ } !5n]Þf .a馝aIQRнq"MqǴkGW .Aq^ן#xg,.ql6=bmr}6] Rw+a +[ZDtq&8eq8 R qYU4#2qCtym| <_0d"2G%AP;Cr/e0Fh@Cԑr<\:dC1!-}"DzCO/h<$Y7M'IQMMpiZ$6Ik#kq-Tl)wfsn'{Ϻ) z&Zg[5Ku'1;uAD)c2Qv2QV?)Ⱥo SΞ un4 V 1>zK,F cV'sīFa1z:T>ML+WaZQv=S%~Qy/G~ W%0zh_.z=П$m]kjAzYelC_9 su]1hO!S#o\?L>G! F(Ud6sKkVg$y'-Mˍ)CA$ن5R9Eu.#p.djY\H_q.^Xm*ta>޿~WmP"q(ǧaʁT5kUOKPJWUk:6uՄ3rPDu6(ȣs# !? ;{zP25![,}},"UWLH<36ְQlo7:x)1&`hֹ`;C&[u=g8f>!yx"d^X_xrTsLs;}E:ΥߪMJ5+,fnZhv[B:!Cftf5B*(3QmKA ewe؊+}`G IWq[B!,oCwz7tO~'a)"h`jlيJZXHF=2b6'|ddpv Q1ޅYOF懖Vw,e}*4Zս`y[,4g8)K#wf ʖ QS/X$Mc;ՊFOS9onٵku{K_- P*'كK0 Ǧ'(q Yo|+6tp0צjgSv\X+^fN{z |)38 ,wf[Kj "n3*reG tg*Z(=4zb(Qxg8̨ϴ忏8nU#RGlCNɟx;fu%{d'ktĴHzӜ[/^4Iois5L53y oig2*(S|R|Xu[H@K" *mf!e L~2FVW&?&nʣP,;w;* b+އݤQMT\O!*sѹ׫W͗,-(9Zѿ_ϑ3d;\7 2 |d71r fcpIHFnx$4~PY?5s6ԛWG%VRv޵t1N&5B!@ãGy8kxwztc;~61h}i&8ESc&k[Cr $zV۟+_)UxiפLPa/i{&]Ϭ2OqRH䀘Wp`3Z?΁ lݖ<5Sa.zP* YŘѿoƬ R&@~GZT8 {CWOt6s|9)8}8/JC`ZE: qRl Z7ɿ߂Mه)9j(ksvZ筧RQfrF!.٘q~/İSnUc6h:;a!-8xU"'|`"al~Sɳr-yHԍefRA8zF)Px=LsZ gh=^j0V:;B9=׭GTa vM\4SFaZ&@H㎌вya"t|,kbB4¿c cҨ+3F4"ث9heNB)yI t'%3jGnVf#E&(6 y2ٌ j~S$vУB}=,m0Mh`%MvrpAnv8߃t(1Emz$C^$}klGOQI_<ٖ {'Ks02e3Tё5.\ 9L8BTZm2FG yN(xgHIieOO!J i8kaQib`un7gF p/bWF3Ƴ,$Gt@ZKY9򡛙(/c{7|"67@2OE$>M`>j`4Zgz+  @wJ Ow RǿU ҉W3/rW72/45%aO=xMJ6L5ԝZ Lmp>EqL@I4*}~KtFˋ'%N{?F#؃b۵}/libreʣKe ߹ȴa"y^.O4$]'W:}ΆqLf5ה*,ëV]U7ZP_U}.1-CofGJe8fJ~FYf'q'O1^m.FZZ1ጚ)&$wW49^=F(OR y~<8+1f~.u+h_'1Nvam#LSeM''H"J <skS"0Rq)rLCB2H3 j=)b9lexu"7wG#0aeE'`5qZB ~Xj&h4i(D|םR @J ,ֶe&Q`Wnb 2=h2 !w6xkG8'9KXwrO231`ZL&X/A/e h4o3#Mf@ 7I$yCQRNXIΗl?)j9 LJZ^V wtd  |3 @bb]i&]['o׮p{=S/>94+26~'-ηg?}L4w8L 5W4Ya.N%/5 ˝WGM֘AÈ<r Y_巙9YtlaeU9aH`GsgV֝.QLyT#Rń"g<' dVt(=G:`qr`E_>c;My\(\9u# -pHb{Bc|p1egHl6 w`;LuE(LzB1$0j=^{R~%NXb;ߍZ[aqL.hHNC?+ &M)ci҅PRGDΘ-ԙv" է]ucRkʹnOnd$.ՅoG O{G?9 e{>qei%2řZK)4%rq : h=vGSLֱ:\_U8<";i$x__4|Nq&(+8Vc(q2ᣳ& իi"܌@gm"h8 )L W〫LPgu۫U LZ+625y,{OD/YI){sպ><7G'؆MOs)8=̱J6zV 2hޟF Z=W߶rNP" L($eJiI>M&;"g}:QҺgOuBǫ=)olX6ɮ'۩~BLgr82cX磕Icj=dgy<7悅9hLHȃq)u lxis!y3+Lo7YwYS>i)|ΠQ:a0rlw;pD )hiqdsh*vdy>sFCbz]úIp -u zgá` J6!oY3\]F ^p5#d.S -.sk7*C~Bv@brL.]PtGL&V3z`dG ! v:?7,e&vG1xVtҾ8~JǫwN/ye|/ȹ t2DǐFES;RFa1 ~shĂ|. I72&׼ R\ ߝHJub>N^,"􄠔!kJc&9J).ehב9}F% m\߈W&yZmFl-^k]jY<)J+}S/ )DZ'2=N'$pge;ds0&J߶Oj}4rgfkNPڱ)Ӣ['aQ2Oב%D0A4Sk̮cTE0~k+Ld:@*P59Iv&\$8lIo-O i  |ȧkЍ;y"h|qD~SP\^P SS\bB5J@r fU>f:w|&#{FgA?w)GQOrڗ TQ ne6l@/sYXF:|y fJ_5?SN  d0Ӎљ$n=aCLhD{k [O(|{NRg|,W'u/6/DYd ]xAMRݭ L0K~u%WHμM.% ͔sMפr^غu,@&2R)R`-h* XF%֋S> 4m/mNwŅqwXzH À32tF2BfK;uǭJ‚en4W9igW4ۙL Wh)f9BlvS&Ċ˔=M Ɂ=o|ʡP4gwQЬ{=rJ~m[(#wwL(nX=.21HSs@%!2 㚐q ģ/po # PB~oV*E8 tS~2S)Њp? :ȃQw\:A̦vm3w 5nf&8(pҤz3zNYG;j`` P^3%/I( YV fY1ݰ~ vS;/]<u3xmYGE`(_os aP|T"8ө< RL ؟d8g':.aC8} ~j{oPJN!P ^|.,5:0ȿb ,Epg"fLrC<T#c~qFsݨB߽NSy# Y>J6ON:u3(ŀ, |K"Vт2şd`K.y Vdd@nɍ# H׋痭)xuYIv'֊kV-/z}dIgøq@?A2lϐtAxc  !M(ie FڋGG2=A>cRյh2Ϫud Kh 7Ć6]來%Gp  +~e-c <h1 k%% 9pM4'"Dj  GU%cqyء€od{-e}>9%0@T1'dhvK&~fӊ?|x%oԀEA"oaَ n)H(`j"՜,prwe9:D=@ e7!a ~/Ue$VټJvJ8qM?OKw?y8e`{Yc_ 9lbD ?MC*$u!?zkd}d6,>|TL/ U=jzCVHu}Ǻ.N8*=s)Q4tPĞ3Zg]qCn9I>ꈤ:ޙȵ 2UT&̇66;Z٩.3`R@pv d#{@[Yar ;z~ EBPD^=ZlO&SYF\X\Pp9mwбϸFad sdU("0J\&7)&qЭµT4p.o<}QbԴ/i2s{[ 7l4z%=:mcp”ܓrɎZDUbP{ZLt!#l0O0\r_,vl,XM^&ENL5F<`hҐV $#KAycΔaE(%)6kAYЀS)݄;0f ws 6+2aFli2#!PǙ"} 9Yy2"t9ssjAisBX):Ԑf[׻Pv6d͘_.60 ;S_*مCUeV|`F&nS\iFD%Y&`4A@iU, 7ҸBENLTaRL7b$~ep/p2JbؘzmܺUGOdR!D[rAdB1*뎥YIE2f0GPܓC7M7\4{  Ɯ) b.! % a|s8Zyfy >z: ^ \✷z;qv/wcw?u~H߹?,vb5G 0h^)8%{A۞H>%ܴf#1l^K0Yϩe@vw|$17nv*؃zHnFS+Ė<H~L<ہI9TZhL~1/^$T*«0#/+ =UPb [ 9k|i t}]9Q&:g%3{]uɁiJ-ފD^*D@QIC(. +vX-KOE R ˇb/;i6f9^9b } W,U߱M6GL+0͉ٖIyѝ_ʀr?^᳥pOD,ERzv@bfg;uuO0eG哏Ggg: !qK9 CƕODM&FC&\:I˭;W{jwYUm=A+GnDO8xWRWkNᰓCi_XyWfTΆww?z+@:@Ń槪<L7'dQ5ڝsJMj,:7`sF^F tBI(w?D${#G f}k.BR d( ^aH,#,JD:;;1-|a/7rnOs]1]/˘Gc ,~h ؝O$aϬFܑMt YQCf]_q``q>/.P;[O4lZ 8o<96Q5_Bݹ*1G14duBDmd<,9? BydgÕA)&[4gFb(,`NgNSu>϶~ l<0E$?E6~_3@wbHO^qrǃ`&gy/YY/s1p |WLI;ֲKC^)~tF[|^ {c7IUbFX#Ta.wz/)#pI[:eH114xgi}`aDkAGi"AvsI4fGc #>gg$, Mob2KKt*̌sJa_s70.0;;GERrӟ&" }#1ߵ!P>\(hRGDږnh4B ٺ'tҍ!^6=$xxrAOL A,"hnP7] asO.Ly ,7f/(&ID%3 NP9)&^1Sf.B9ň5 &Sg0 W'DǝlK_<\V5>3s.+x*M%ޢKd3~\jeyf'|1ޔ7!w)Zsol@H8-xP#UX(ҩ "Q A/3xm@[\,Կ !G'hOQ2B_ۈvќfw5;w7#g8+TF'xj`s7=Ս;F`e=.>:۔nb*}S܋rSb&zBf9 $e|) # Zk03=h?/tmſCvih"w\o[) S=h m'ð,=>S<UVX+,=~Ź)2a^1޽]즞b'.641(nBDk&$N]f,I޿Jf:(̴4tVa8 Ʒ jNꊇG).͋ų;nhNfdq&PPKW+9z^Hdt:zDLÂg0ȈA.M$uO[kC Mŧ#q\WHV]Z-V[3t0.1ߊKSmi㻰ٕ"T P@eYw<:Z5>YA9 - ^%|IysJKDD6/FvQ=}T0fV^U RBG&,ֵ3=nq=27 =ukRv70y{9X6 LBzy!=J[xNޏd\D;[rCeXҭuZ#;i+Rϩdz  M_Xvg71jE{k wK=x#`#0Y3.աhseF P?j  C!(d~+0H1 d$$=nΉg|<4ּ.tsZ`κAp jgG&gu2s}iQ6b_kci"˟ ʱ;83BqnYhrY.96"GHg\>:~&K^[eک Iگh7. S2V]z>> dvv2&V-eg(;z0_|Ot]bֺ(kP9kOLNs=d7_B 1I\[piHW̝{qPrHD_8lmd 7 d'Yq„luOqՔEn>f@q7#ݨJQĐZ1ŋrr[EI)q|G ]YG#(+c5e>;\?K.*o4w 0hX fdsXbnuC0 ?^TPiN -Rjy-@81|d%,_h_R.r;V0H#~ö)EcЮZ̑͝?Bc Uc{,NNogȓǥrc*LF-䏕F-Sk9b\4iZE[ᬱsZ5x;~zC=k.7NzD+IڂLgi2iܵDUI+{li cx ~m|v]D8h+-M48e[ Az AR 3'nCEpfqEgK(/<#9f[|Q!Ⱦq"$lݴa + I}"}F!l4p[,];23[}f4qB\#ޡ yZ1A x$_Gu`OU\g BS`]0,qJLXՍsy8/gM©o2O-JyHsS;=vHߜ+zd VfBlڜ4D1gwZ jCLf{1㵅kAB! 5ʼn6}T`!EU%|agYP{l]]/p8gK^wuuN]5at1_zo3vEвxF\"_(FJPlچ[ QgwXc/Ob3mJ-bvcrgQp r4Hk 6?' Ҁn]c_+y,K44`~VmCkU%Uc@h?17ōE۵ّpbC#(6棲O c\wD7+N=VYa QV^+RYpǨљFό%%+!4IeX]3MYYRɏFq Ⱥj~<%\FpNs6T{Miw8 QTy7Y~YKMY^EhjR<Li VyJ|on{'sFbKG7()%f>4 Z2IA Jn Z P`ad("sۉ*9*vf`!^0 X rJGs8OEz)ßO! &fl7ok8]~PxSIMsΞumqڭk!c5QK3P(Άg! ]4C.SwR |ӏb#xY8U⯘+4"Y3 >lIϺߟb @6P#lanuFgrBtx6bKD5~;Mk;rR{Wyg̡dG L7(,RkTAs`@ Rt_U4[!"UƱ>F=F6sINgZUݵX.G͋hVۥKؓA>ZVq ׹l6I@ݛ{K؅|q,U+{y&k:vl2ώ:f.~:TTVU^ eNG~;4W ,(g]$΍3y!W@rRF +M}@q*v.} Jq s ]P: 9L׻KOr ѝ|DbV?^>G^Za3㓼7r&N[7-)+T"ξguvcJbwPg20!EkAG֏2wG*l?+&+StA6x p>t 0U{#vZ o'AfnVp9R![$,b& ,;ƒqt봑2?%:K9<70:J͎J[{R[/jª ?ٝj]5ndT<ۻ>'aLv4_D^ꉦ䘞ښ`X(t3WзK &3NMK/_8ΐKam:<^+p8ClyGš>m۠~2VJnZ=R:٢_+i5y([_r`$ejRW)+UQ|Ÿ,<(+bobt f6Ȇ;#[5FrwL̙xa\(̣`ʝِ>9SQ?ֹ\_E*vO"N2x T{͌y4ܹgO@AKX ɪqNC@Rd0iՠIm*|:Gn%}i/grzohSo+)2c  O6%KwdLkMc.^f\pPf^]^̦Zˍ#ID*W&gÖMo<3U YĬX˻_ 0^  r5t9hC8ʻ3M"}Q`28s n{ b}ScNaKٹH[Vo@rBl/ۗ+ Gz\TMBdUpTBLUVbu/|PgECӊߚbt5$l |9:KOCb?%bۛG1mb]Ufa$WDcq]|5kntG9Gah)rK'Sgb|X4 3XnͿfjޠ^4qkJ~@|X#6{(5XO|='@<^ T"TsZ#g,mw?M`Sxǝm,29e1974Gu$ j3mqC1 Ǵ%rZ2hb6HS/]WVtih}N/CPyejt;6Z_ÎSzAp>x~{}T#EG%tH{ 'I\";WV23>T\:XĆnYmԂ^&;G [Ӝj$xY.՟GEu 3@ mЎ"P5uߓ\GhM+EL\Hu׋Ter9~!u:`xo )mg\C#c};Bv4dONGm0>O#+{^`Vkb$fDa4yȴ5>L$3g ᒱs]g:D$?ܓ׫&c13;^;WY ,3 (40B3yK!vOCMZVuVWޞF5te ٦^m},G9UZkI=jJbj@VIt'JlYRrGݹg:~*k!;R[ @T~bVpyEgLwKył\G!n:&QPvր2~P~Ȍ'z!2 O=Ĝ0~ W?<0y䂿JOn8h)hu``I(p>[\OhҼZ/JdxM<%KA宂y *Xp|cYlREݛE.F-N.+xi{ڵ]Ⱦ}o{Jغ[Fg)MMB"z`1ަ^;Mg>{&W~T̓9D;2>yTapfR|uF=_Y'^[%>WΙA$Pq2ִxWU~?Z886t~I~}|N9V>81p,tYbD=O9(/f?zA{C}kXU,e.2a6oV'Z/y@F2L /*gg@'d|tѸ;yB֘M'#zCK J^_+șsKz vjp~x7-Z> I#mH0N0#چ<{ߪ"!(tpЛ|`r#|44umNoCm}c٪:Ղh':A /4uUU4YdP^2s}^_ڃ9 xkg-ՁcHC7:w?|䯑!L> z K6Ibq(a\ $v 1)IAˋvV8UTF~y~vYz3?]ChU LfnQfM65>V>9\ l`M5~ {mJ́ƬbͶ 6+doM$+v%E. gH2LܑռH)\J~-= $z*- SK6l#< @2(4a%9~\Y.SU@'+NqL;kd-| :n"o. (cQE3 f*Q'(F= LbnMcJMrvpX3%9HmIKfĭfTVBzZ\fF(]e~kU&!ZE<0<56 3OfBR:"Ms#&Kˠ.Dn:Nr= bgWl^~朅C:B>BJ4To37rm5[GUNV V~>h|a '-\Hhw0IZݸkuܑfTXK(P*:Ef$`F;-?Pu_ &TkMuH]a_yrV$c mF@?x^msdatF O"Ulq]+^2w#O_Ir.F{恘&z#\';ze Yu*1+<$U3$1Q^:5xRXAvyPFoηGxè>lD޿uK?svUK+g/s3@̔((@Z V7ds@Mğ2M) |&-C~,aY ԂuX7)38lSEU._ mӯZR2VY ;yz,q 3!EH"]G@t%Рrd$%4FozېZup [UCqt7-эMO#;OڧH G`5$ϨC1b':byw yNټfS2S|n mA r,oՙ9а_5olYjE;0.LDwڣd냨x74uc$nXiTOu+u.`Y-(WRb4c|}aAn! Lrq9#<'/ nDALH3Qv37ј=P|c?p3aʂuэ}>]ee04Iv[YC&iANVU*x|=`X"e)x&.*AA(>6NH?Fn熖Ġ!BhE2|ò%gr9/(KbӿHU Q(rg vVqjջam`/sja='8*'F/T:N,:PK0̛!+)QD^2)|xіP)֖d)"o4JΙ0] Fr"]ɦ88:š![X(mሧ%86YU9\UMM33ߩx擐G{ePӧۜReIjN}rtL@UAY))FSxo]FyFr=y١z\g9EG&jVUZjL{j|[ :_O͸!N'hm3ǀ O"Tx: TU@H̢MNKݿpG)Bc1^Csӓ_qḣHP^?+ߍN> ZZd KA.)8E&QTRYV*d[KѹSZ2j YwJD&1-,^M7C<8*|{ϾN%C͍udé8r }4v]Laԝh%NO+3C12Jp3' ]f:D*E;o>b,,( | } RuRپ"<@ǣ3$qH6fT&OedF#Lҥɠ%Ae ~͚ p)T3{0Êq'&MljZ*gri6 w8MFMP>ø_?FΜ3tFBY:YtCIvcuy~uB\v6j%ҧ(z{0ANI!~ӵr7޸ǹF^^+U˻ш5GǮ43Pv+CeyVWX& RI"ν|[r|Ţ{8R+WI,| E7w0>HpWg4\lڲp#7 DrC MWדY{D"2El۲ŷ|R0Tlg=5ICiXMшogC2a[d:+1 ĺݦ0V:0}ƅ0/NÊ$4u&+og7@[qaKSaTY-Wv<9`9bp8[O;Z1lbcDReMPMIKHYW2V a|$t>c5Z {rq>$ЕE{7^p~o뀞L %4w8BM42DW\Fi>iO29OFͅBZaTdl203UH#|9M#4-uZ0ryBqWzv"@Je"qrU%kȨwֻ>w oЭ )3FɲLRA6F;)!p=Hqd٫ l-ymӑ-a5Mj-#;)8E>JWGN%/d~׻9L !YLG_+vEa a ^qe(h/+;%:`h+cXPc I ?#1Ճ5 @n yu  |X~ ~Yƒ xq0~:qc_ +_+YƦ,+*C)8_8YB>IF Nl !1Ӣ9oPaY 5Xt;nC$3 >rYlb ;THVM~6caN<$:,DUX.@0 de_OkJΐ_;[I,">'qmT)L׬/lp4?Or ЛbZ]d")yȰ٠_t1U [Vd95l h$F0LM Ba,@{:~BYuB.E!+}%ymild3Jć[WHp0ï]o.³1aZi^Ybbt#NÈ#D{Pi7Ω_}],ZqS\KAp:Be69VBσJz{9K&UbE`%a$VƫOfЕ"**\# VF Ә&ӿIcɲbo3כӡ 8V"#axjhvmXꀾ -PzeQŚ1}X)HsZ>dYFH;1V9#R{?S¨񣆹b54M %&+f]%< <@:?0)s&Mlh'b\)-q#v,"VdŶ`a⓻ s lvd/9]-s1Ȁn \,8'@nfoFv@gvrd0.2RPɅS5#d^3Ď tJ{=Lxynu4RA| .$))[PS٧¶[s7.- (pT;^\H+z8(B-hiCꔵZ2P9& @Ne3KLQ:<8 .;8d8jxE?k dx_ux&//;G&q\zi3"SzfPąqz q<#7ҩb/x%x0nGB|fΨfp] \/oiѕ\0V [k\g;BH/ F2)Ys 8 "h>,Ms)pI|E4 we,Sv (:Ab#r@Va5>񯮺/6(\!oGr-lKլr)zh!ZmM|Pk Ts>w߈>:]s -L 2MlYBJlhЊ"y  6[Ŵu T^@a_ )%'7!Sgdp)+<]͔YE~G2mn8Jkkdi &+Be']"o`^5>١S__~rB Xs48..@,ѩzo<<A#qQa"3L{PB"ݩgp_/qԖ@j}|N!!3G%M-qڑ^tp0C<nN녦?A+חVmx~,@FYl FrwTOl!Vl=RiT|:c0 ^9¤V(^8N>k`nDz=2x#rlIwgdq1р-xBQ V ָGPİ&vwCV ;U}~i=\חEyMg+(*! ٓʐqWgRlBLCC=ՠ,0iJ44&[1k)`)f= 0+Y+;;V_i)ߘalNґPHoW] M^aqVCk:kj+@Ggw>]9I{SuA2ۈ%Cpl^w݊,%j ZpG Z_(9#wi6rىs0Hr_ X ;o :B)m 2; W ba=L4i]WWRAj𓏬\ܓىMO8%Bj~n\},\i,4Z=<զ ,ݑQ&wE(=3*Т*'1g6\j a25b^׹R&NEc x-EraEUji>zL;a68];h(gkB_MvìA!fE&es_?l{ Ps6t9yZ3փ[QlCć v}yx7hIgv@%8TBN}$X;,3 0P[|˹~?:U*E71)CVܲj= ~˿ Ko[\ XhiEiac6Y11gLG~r ^ǥ|rĒBOU5 W&:>_?Hϱ}2A c5􂹈2zy]F``P^WU4!8 +dE.\Aiu$oa5ɷ![;I`'IӅyN!-kx ]7ޟ$pS ba7e('Mp<bӞ8D*#-,yi&&qlm"ql7\"Z6M K$F+$阧W!U`Y_kA_ȃՎ4r6E72!v:p.#l  ' qDD( X񉿙x?o47O3Ol/ҾJTXGp!p-&[k!xs䔵JYnI- 8>~MT@˲&\v6EKc2o6@bd#\1l*EC&0:8VX8\c# *?xA)uE,fY,c`FȾ*ʏ-:&d8'?ag,̞Xdb^i߯T5Ԙo(k"/Q&lod0(qDR$;2qz[gpN\ W6uӖQ53Dq]wԂ+sU|'Gn<*t8O1::0d-'sl#vć$01^=cN5plP6EvϙhXx70,M9ʱ\k0~< n(lea |Bǖu@kkPlrM3+TVY35IpW#CbFO\Q˔Arٝ49C6x,yD>a 2EN rx4%3ƕ6mp` n]Lᔙ Mn<k5<տ}1K.eGg&g]XE"%L(KMkRnl~EVJu&2PT(%mx <} Cigʲ.uI'J3)^4TV) 1w h0n.5q3O:,F>]{>FƅbdW^Ha\c/ B 9ϰxf# :'l{!l@F0"Lc s sq[&eHUmcٹpq"(xqmrg` c2*=O]I pbwE*9jH3Ѣzi kqWO ycnmb:J]hя뜝$Bsw&Sj}}m;llX*iI Ldcw9זlEr·cyDF#h00P!C"}. oWtj46 0AяY cw&v!n= )Qms7O$^0UX hךŠL*5W)\_0)LGۚkLyDVtSG~g*h75 HP;Ep˫ ߆Y e H?U9)$ 薓T3.:5`7 s<L.O3?V>vqEҨlS7hAn{Mc2[!l'kUnlǽP|K5+(gaעm_M4TQIJ&XekҜ|gs.vYb6%)vLD5el_Ksgs#2!H[ ͧ7xV T>bQ/yQŸમ%؀a],.D{* '.N2aJ&ŽK"hN*7xL0sۛXTԁMPksbcSrmLnzA dLsjT56=Op޷/r )'C9?c`Nx Hql܅~=nfZx8"%Asz@E+ hn7upD*Пڧ \v\-?űh%8 ȺA}9{8 |"bO,$ZPX-NʗnT3qό.c.F">9%"lLHdSb3#;eudbu2~E$&VM0WG1E8>{gXYPr+\{[d Rm֣O\9EbSFW_K [AG: T/'`<ϭr-K. iyՎEקXM& y/̦Z܆kl*-6\J&OȖ3U,Z2|@C0iS"Xߋ]^2kV]c=&eᝋPV$W`lOƷU#Qg#+( F}(jߙ è⪍&} `[nxb(eur/7Pps//H6I5x2Pfv&\AEǛ9Ʊ.^ OobFkCg\68-*վZId(ߓc[0?aۋ7 *Ă5Nbl՛TF~M|y4tNL I'ƔW7nt+WmO7uSQȻ9GptzLVI2z9H²M7uzrsp?x*[!(I v>Zjƀ9qKKXoi6sF,r<0ǀg4UA;ixj@{Ub?Y,@ ij]VKwD{]W h`;{-MeuИ r$O! vjdeKF3ry/#-LILT~EhJMN},cwo^6y~<*ޙ2% `TZ,ya/JРɌD=kzb"Ykg#B-?EywS1UmTgbLÊE=ZlNb$&uhpF%G$U ,ݛ)1E3hc[j+婼A0@nkMAq&:2d^KM"o`X}[Vɦq%G⎺zB ~u(o0{ՃA&)#9>؟> ԆMWoCDe$k(UR гBζĄBԅ _ $eQ2]Fފn렮a_SyyP(9nݬ!CQ>B^u*ɤK/+1R$vxa9rcXu R؛R9`dS 69ɲHS^@T/9duF?RE߮3ŏv%|.^ܨG$bGpQl]֬]z`1F$ C$ sdP o0c]G,@sW)e޿wm^;;&Oa"N1Mn(9Klr tkER!j 2Uz{ޯJ#qdS{Q11Ԍ"ن mj6=0rTNW@ KŞ}:DDk ?Ty 01r3Aaa?N80_XV/-T\ .ݸJ3vLDSj\FCFWՠ'I NumF>8j݀dR8 vs"j$dg,Ф;st3gJ a_kyn /\S3Y|kI4,3@Qq7$ Kc᥌ݍ>/Aa΢.'b"^+8 XXц|؂8VO9 Ii? $rus8J{nkF4 g%bD>ٗMD?mDw2f LP3I]_Ym3E8: $|-͙;wy/N.|cIG[[7#(G"Lh*QPّtT탭8ΟlLTǯɰwaLtjs*+ Qri 1W;f͂dg6$T/iMujǂv:yC=ٜ"<7>'{z CHxnHJAz668b\?5OWP1V\YB7UEkB-)tp=SBn &!b33OM)‡J7~,?YO[%MolrNM+~(Dbn"Q:zB[=i6$V.Mgl|p҃Ϧ?ގWj_Bal0qX,Ra`p`ZV:\Mw zL$v21pܝ(3U|-,2ISC`As!֖*H O߂U4Di}~btz%Q{\I+"쐔t<ơ)HlzPsi<mX" q]SO'se)b 'A1\Ts[ZT]|R'!*R1g@/Bԙ~#i7$)ze| lfgŎ)x[w?׻ m FkD/|pLi*]B ~A}U8 F}ͮ)3w}hGc34}O1LN㋳c oǕlbFwX-{ūHOIQZ`)/ɩFlJku6Zc|Jǃ-)Eɇ 8ϧT廃SE];Tﯧ9/VSBSl }|= O\8yũ|l'Q #q.9N鉘U:!lue9ȧ~=K6ᣃz2PU}y=(P29MNc ?驤 MY LڶI]RϷ%8&W!q8ZE*op޳C^/nf\`oDRڒM>4LO=Oѫ~]GID )ssOη~LDoj۠pl1;W@|Asf}u87num&)80Ŭ,o#54ɹ(wqN|*g {zuH|u;h^}*R/>|AW-V3v7>t!`,G=0dzcbEOysp݀?RQy%/oGJkgf@=R4jIL[Qnv!KĞǀUk֤Z\U\Kfr`y|>N66 =\$GXmƉ`iȅZaQ)oWQu)+ɱsGU$5$Ih>8|gkŻ1xp&;nłZ@G"ֆѲ_9{$`~|) W;`$SC:'n돿Vc(^jrf) VuM!ho,X}E8 @x4uV.Zyʾh}&*`JjK.Pdb/[8d$`:)IW3-$:2=X,k\Ie.@Pι{vcFz1HHǜ#W@J$1Pd^"Xs!H{! <t%v{Gϰhxc[  Ի6+qFq'b$=i%fݖR2gPtc d2y\1VQhCU2/ 9u=)5D`L S[@M )mގ%st@P:oBVCU HZԉLۓ2aޥˀ)ٓZ96.g$%‰oUt- ZZt8i;mwt)bMpJrU M  "*U';DIl2 #{(X1E|5sxZj)>2Oc :M&W\kz6(F$)6:Bt 퇍q S ~9/ ;4b&fpӦ,KN;MlXw@Τ7y(1 D{Io,m*X)f\c mG@^p ֪>g`^7S .85e4[KZrM3pxY, bϓ>b,A)3Dk}a9٢13^kǐ h nj*˧޶S!wOՇ~Ђ!Af7M5䛀@u##aHԆWgQ fLCI4籍m㣋{f,*+ z81HxoP-~b&Kfom0o2?{3θ/xPioeKeN"GT/qr=@#yrh"%:l40Yy-W\XiM?(񤹨B uŗlZ;,T9P.][53 ΓRmd쉙i;ڎxD`C$` Yo 8jgА d6GwI2Ŝ,@̲B({ϐIn6P}K/o w2K;;D3Mevk,;$)+!~Y);&#SFF v'ǚO^ekß%)D^EScNP&`iǮFC!X 669CocBA bUteasXͥ{8Qo$b|v+G+H?2B8`0B&kCq▼?nC>}-wѡA%iظWrp>xbQ$`UALLAqf; ȯ]A\w\[g~ܸBImsCbAt?cU'&ڠiړ+8ImAZVW}*Y=ljO?9M맊^s+ !* ߁~O~Jj EY\貊dѼeU'0}j/ĤNm0#Ռ=Z? Ojò Y{RۄkJ N}fBجgxRh ?ErM;4N`d@NDR)LB1)safܠWiM%"rˁ;X֓~H Vh;*a}{*`.  w.rd~v+袼N8]Z\J( $#&$uk2)~(ӥ4*,0y"KQPeIc_Z6")QeL,VJ^lgQ}]Vmx=b #UB2o7?9>W@/ʭH2^'4dZCef—R\ͬL|鏎ɋҵ)>3ZqI_,ҹϞ;,%.{>+B ÷b煛U| Fʨ5WOFyP]i^hU8S2nἼ*-:#ȯbb,?ں"Yeg&0 ɧ5ڡXmy{Mm7gk5.캐c0y2.}Y[{ϱ9|?㲌x8OQX=-Ju6h3~ޢU!jx ڄe+ȖSa~Ҭi[ ;5T9X7E.)l2HڳCw QEZ }\F0jX\Ǖe|,1M[RWUk޻3sr2Gm,7Q9 S" >:˨*`Q$ސSZ $O8 x4Ov-Ex>X6Gۣ ox.+%GH3fx:&RNԹ`k)S̠2m5Y"|fvFX]lF~*an>x=$▉ZikXwH_*,{d#ShgA}Fyоe޵/0ӄg$V]5]{"`Lk`wUhRѕow(d\ZS1xn~tW.>Ahm`N">W*Cj>7$ޅ4$3%Bd͵Y+;[sOƋtK,NsƑ_z  [m1K)4E࣎BQُ]5L{giD0]Zʡ" ^q0CdL?wi;t0#+xXOf tE-낭^H_lw r§  MblHlцudBxm 4)Ĉ i(rXra2WtrLg<W0,Plo140+6B+5\@ƚfqCQƼ,W Ow\ w\Dw%bi&Vܻb؝nY*ѵ #2k7);T;V\7UʨDwٛ+LЙ(? m#5)Ye=)ٗI@l :oO0. NmTG rMS|L,S'opN$^Jўz$J K|=ߑ`)lVj2S2čjW, _##^EW2ܭuJsF8P| UGB6N% ZFAA*_xE >I5ɷ-?;`֍ݒ8cxB@8o109̝Dk]'L ΥC-%Suc7 @kGqp(S:{4mpcGCr/oݫ_ڟ<Ԇ0[PJ.|&|y@/锥JX}$RahOl`[ȷeC] 欠ec0ho&CcV 5ةcx\mYYNyBɿG:U BtbE*/I|P],!ǃLb7,bQr#[ib~0e1kὑdK"K ' ".=5޺#Wa"9)6ʞ4?E$NNjcɱj(`Hsn~(\O!oy)F:Vf஋+PD Unw7"0?,"֭ۑ8s˕ęFY X,E ? ,qP7=fy?^tmLsW oJށ@>&Ek(\`/C3dL5-$.I;g|ƙMNs ~ Oذ*˶e=tSR:JlW;U.l9̠cu@ԡ`8d"D>!P?nBi1 udX;0q<֯>NzSI9# [`./R( :@9d/8]+ 'w0]1\G6X`J7/PBl,T&mT[?sY^K~un;%-o:%PdweBLĔҫ=K_k9zzJg"! r,2)TN#_Hy[PERe+\UcG cٯC az/kptο@hO əl:Zѩ4_/j1c}E4;Qf#* e cmwvQh힛3FF>ؚu BSmS#JHcvImV HiIW|CsD~ xդy5kRu:HhmymdWb"&R ʓ[8uH.:0iSvd?Ȣs~9Nv 9e參&譤VޢsmAJp_"d}㟟~yZ,jW?]ф jG|1*4 i{ZV,6%v=?<隄qg/*'Ni&̦YHpm绹~ 26ۡ VڒFfݙmI<#;'N. "i[Q.@vAx9׫ 3EcBĒZw* &2yJk؋]ο䁫VO@S ʜP~^5M!SF LB5P"2:LaOJ;fBo3&rmc_dP57I(2ExN]}٧Ps#?ukFgޯ8f#¿4 BQ)@ZI>2T;#F\|^kXPng X}7gQ u*[0e%kFx`Էa΂7_GlD;ן4z"ۭ K!pWS{neZa]:UWzv\Gv}%7U!L(bE_ʟa|NFh-e4cp 8VU^˴g.K6ئƘ^{ JMj|/8qdRO;༵"3$sݬGr6Gnll_dE#{Ghzuu_[$LhI3 ”Y.ȭ_*ws߫_<#dwt(=1`&t>ٕОf_:>oab) :%Cf紙} 64BXa8;P^_z?R|s_/bSh~.LJISP)փuO=\I*UDR;fngOQC7R!hRwاpFJ}[JQG XW;6!.ژ/5uS{tb$.Ap\`p6L@)SL޹!;R?.닧̍O6j6F{Ȋ1cJgAR kӸ-@ m0l=6(7,F0Rԅx/c_"D9&g2{$EgO7K65.nQ-cӹǎK+S("XI"İU!0GdV]J4*mSt7%KX0{Q%)$sPBwfp0z<dBġ{/SM)ayw=#eکqJ@iH 1QI8˜kaiDJ(U9Bhyk!\o& yO%,@23|sw7X(Ȓa9fL3465Df#[{BJ!̆X; X}DOL]kmGH!ExɅ uE ,YxY%8NA$[&0j({ym:nvsiwGN}l%%n cDvk(خlњ>"pVߡju~-&\?xZA\tkC;gI@G&ro5}`˹,`&3NV͐/9? AitEPἄ1~\3A'n})Yop BQGW[4J& (W61G03SM 7$-UC,nS`Gzo۟ pf9σ#D kĮYAI䩊2)ri5꜅G7zȭKws}]Oԧqㄓ~xnJxOkwgg>B?Y xg{E2{Grgnyh ב&] +ی_A =N1E5_Wm͑g{'+ ߈&Ħz?I42s[V9kB/ c=VRsK# G)T['Aþa# u՜oldor}G }?6 ^YHe♡ Bsg1Ti0V$fTT8 Yˠ[*uZPM of}I^WXJhiXGȥ۠g~j󡮐6bQ.[1Rg6B~4jZǸ_ǺijR5UG>_3mU(!F s0L[&,{!2p2qGG@3~o40$OI ? #s/׍emG!%9bOa ;k1V.rFW#5)*ųv<9V{\Tm;ԱCR8ä۵M.ϢN]X24h:.ghCpE{ *7qBkQa]i<׶u>kS٘{~}s%"R̦h ft.)%U:Qɸ0!{m~蛚tNr3U%Ta2I_btAl!fNpգj҃yl.(d6dv 3l6ԪB0zsϡ5o;.P@(EEz_q9n`흓HÛDWl͑^KwM wo]qK*F+mjFNĝ1p`5P(=nbm0vJ@UvMCU*3݀-604L#k?;.[QƎdQ) badkJC󅔢X*? *(x[@R'ʤ^<7ҭsw+bmfWަy|0&tLgBi1F"Ԛq6ێ)n{1KINO#xt3ɣo\UeNwQ vR; nRĦ@<*Ϗ lj 94(s!nܝ %;VtW"Qj`J})mYLs5釤7Żïbq`cĽ /졖t?9{1Df !m˜R&Q kiL$5%g,Mx Epę]r84| e%ZRAW pۆ\ˆI ti<8}I}\Gw/M^0js9(E:}o?)xߩE & rKuL3Y7cșrzK甤U;uU9FK% i6Rlh;.Ù+s+\m8x4G xPE^c0VI ~nb !92ѿP;(O})YSܟ;98|/dr2 (m%4ydb\ڰQ`ۛ 1w4#o\ԏf" bN4UMIYWnqR!Y!RlL1 SƸuCaFqE> 89JV9qSJ2Eɢ5:X4kOIo/nN!lZMxC?۬y:詌dqӪ JU `_*n Ti{hPS-:wYE=X?]($nlcPughIf -L F2inf)\ y=ykD 7/r[V{'d}R$8swτQW"6$Ε)ʈ"S)?"8G~e9,IcC IEjDd vB :__(vc*G,Rv:@ RF7Z&گr8j=`@Z !{}꒸JPڽ; jRGb2Q8_= wt%޻It\X CbG:Æeԃ5kCJ{| N<™{@?Mh< m\WͺS gɼʿԲ{NPeVr,U LtS^,PvgqLfVyNuu(]]@R>ᐯ96*nRoҾ*#[=k7֭b lȸIF(WA{n&.6^B?8cBSE3T$z,O&OrݲpB?cQ8GqZQ{/"qu=2v,PyW{DAqaׇ]%XѴSBL[f14c%'kh #k@u ʶUhnqȐ̍le~`ziu{QOB$9u|!;^uu|)Ֆt96VeTMc\`TfhV׍eF%>tOڐ >X5H8{3;;Clg_pjadͮFE`& sK#)Hغ~Z>[e_~+ 6A(X|x6p0ѠY t*g fmcFMw?*ށ:/<4w9~3PÏ]#Je$fJtV߇zhPW^KwϰWh#jTJlKi0 ︠, Vg#֩u^[~i|~եt]`w(m!]slrU5/ƭ t:Ie_iޏR!;pn|:b[,(UPͩU3oW(oJVX<(N9@JbI{w e"+x‡^Ey)w(u)"v2TX9gЋq,olD %,Du.' 4[/;l9kn/a4A鱇a 0D.>ڀxW/~"zhgrۓ栵nBm'udxB?9%_|CYT`l[כ߬3iZ"@3}|̕L'%kڜ_JpT\eb" G{-FFء0?S?!סH|BK #Av*[bo9d@,k3B<93۟&:R^= ^LVr}|Ӕ{̠<اJc^{^pHQ1,GſqۘCU 4o7 :8U}}p|c#*{~YEy̑= G9/ T{M]ub9W@N!A?FbmaA- /R̻:y&hBs+?-2Ai-v`n:x{Y:قUVqV|_W[aw?zBRʦŖvE5)Ķvnaki8 .- vުV6~'}M~QHt- `%;  Z Z4l¸dgf)r_@^[T-A{|udUf'쭸xP\\[?L@}bh-nZbT {m+B&am<)]qH_jլ_ϕR bUWaxgh񞹪 KwWrg6\|͈^wb!D0yWmWuӗ}I v7\ٯ`+IsjA!ӓM;1Y566\3P\zJWprurC 4J'5ck S-\(bb)8>FzA dA" @PLr.ߒ*j,!^ޢWE;5ek0d*M \arsm+)̓4'pbUM'8ƓhEgoD|g~oLunbpF(ڛlu >nbZXV&ݿwPƒ$28wqEyk9SAk$G%l# gMIaSoze5Ty a|udk|?[@g'1&/H{>bFW hu#{*mkP&7g°\KboP;+-jhe^G",u0:ļ~= c/#zS Vp0\n|D*$#\T'Rړ>rߞr:G'Zu, /_cO!4w6H/:pg OWq PW ߓv:Bڽ4Y`[)<Ыw??Q# P1H}e/m3?_ܮ>zHroòԩc$OoH47Q18KwbedFF`XlLh,[smkyAթ8Fm8n.[Okj45J%B+ȭfV}bF=NUO1Ϩ⥨U~:G~)/K:B?{h i5 Ҵ![v؍ui#>I44]QС\`V]p.v?u>،L=6lNKx*l2<ߡ/oV_eOXHiGcW426iNHc(JQӤM {=W 0 :uwEwb@u䙳! 9dy~śK ?Dh Lj73ɭ;I Ø׃9 / $SCwQa~ ;օ=|EfQ?og)_Gh 5VQED 9 )z6e]}\ؽ:lvO\(ذms8=kC<eӛ Xy A *6f!<zhH/[ f}DNb/6(}xa6M#7LT^8?V ʘ%9fb4rv2fb֗fŸE7E2`1hf2^nwC"tJtck(DV?z-b|Τ%Kց:@,lyRjڜJGPd&A a1mKW)~j̈́B#rv뮑ֆ6<Ɠ يmQ9d6b+ZLwE rx5O G*{:EݮƾLE·GZHJs3c^&ˊ;?&_oL{W 'T6o![ޡs\Kn Mŵfցw/Կnਟ:nV9ĞѦGj9"̍Of!DR uuЂgۧk9l?=}WN12qщ[  -f?isA@g"ЏMBx(;Y#[M d)%IC.wZl0þf:Nu-CR:kpSG_kdzZSO@it)#畂z>^wd`J"|>"p tS&m.)i~$/JeR:(،%qbrBX߷Mj3? eo}ZI H♫="1n:Iyk$ /> ɛT%x֋-o# 6mf3I^Mx7{LU|AȢwǿ>Kwj|KFWqZNi7 il~5lHo$L՚?O GHU6r1߂%j!y}8Y?`Zhl(ODu(R`dz$]Vy~eIR0&$`߽1~vg h{UE@Ε~N1R3:O|tނg\\ %!1фunonpb3A_f IbC($erVYEIEL-[W,n Zx(7()j8?MgiĎ󹓑. BSxKZ1!d8J~ 9X‰0/|*΋S M$7IC?(W??nJ ľ=vZKw}^ԝn x2" |?<^UғXtf %yov` =0!'9o uds[[L~h|\(2 i/O6)!4"OPvi].hӃ^ !xlDp_dWžElZؘdF颂G_5idߢted=F4N|ʺeůq/7>)x)">[^Eo +w? [XF6`)3H:~6-.:6&Z]گ۹p>IԠ'鱒=;m3>FY򔍝b[燲0*3̋jE&,CQ+ZL >D&h hizCds%y6ۨ n.#uG=#cϋXӲ]~_XycH_`̗` o1'`<Ya2?G]K˰jrD=ϒ#My˺tӀ 5 +!:Nwo]®3i i [EEAC30Vˋә:qliкc-K5eVSH1i'`zwX|]*+$Njy- KtUtl(\sȂ0Z^^ūig7W/gp!6$" k +OGEіF ,n'ncV0ǎFi׏l9RBcG3 fz%칞)$'' G,χ%qMh~` Ll߰^m=fŽՊϖ5N5̚SbSZ.W`[O/E`oawWt$9#pUX9S[;mVg18Dj7J&C)9ɓRR"V F{0<ռO@c#JIK;FB=˯$5,Jp$DS秇y;ϳc~-FTUH'wh "%!}WnQ3;ͽJؠױܓQsp>oDm|]^L12iHZ)QGK~&`Cyox5 iA*돲7GE -g ޡM'tN`P~AH eH&#z< "fÉKe Wu`wgpiן[#ua߻ 0rtxgh PmDgۚnܡ6NbikI"+^LbbDBQsA״ _گ Ǐni8iJM+jsyG?mٻbiOVAułGY7 }a¬C,lE֨&n\sciZzEV_10A0w`A4zzٽډջOlUWQ*meѷBȻ!Č]ۊ&)w4!GaLhy'E1' 97Ƽ9~'E_ `97QY7΁~vj 83몣"G_fBb@[HD"OU8",x'*N"AZrGT+PE8l*]7*.Sה(1X4\Ggrjؓ|8*ԁо{xuQ&F+2^HA}MLwk K˃L&n]'oS{Y~U72_ T,hjTjg:ܥddC`zlȝTclk,cGI;/iL foBkZP "{Aם;ijN Q_[J'Kx16@L~K=V s{xfisDmaYؼ$%&U;*m#v$BïvkJw^<;-XF q&+۩;RCf!WDcYC̄ \.W~ZpNb @xчi*1.U"bykY;ݎk~|TޫF|hSiX|m]%$Y&J}.&5wRߋΉ YVqH7:Ifŷh -g; ;& GC 4p:"@1ɆsHH~7wz@%=uN^bu xgFGӅؘޛ\HV;0n̸3L\a/HNH'MI0Q{bקa`B3.\3ԹO9"7ݡ'78X0wW;7R^oUaJZg?TӏjWc̼0~٢>fj~H 6XZ{ @9.Ci4aL*;|ZoLmutx>pwkAc?9) Z 4 :6ÉaIx#[sq֕^nTK؝2LօQ؎IU_9P0W] э"N||4 fӏ788O=d/Jh437I&fM $:7sL >&yr.ܼL+x2#>~ tgc^4).dropdown-toggle{width:100%;padding-right:25px;z-index:1}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute;height:0!important;padding:0!important}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none}doit-0.36.0/doc/_static/vendor/bootstrap/000077500000000000000000000000001423054503100202415ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/bootstrap/css/000077500000000000000000000000001423054503100210315ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/bootstrap/css/bootstrap.min.css000066400000000000000000004327551423054503100243620ustar00rootroot00000000000000/*! * Bootstrap v4.0.0 (https://getbootstrap.com) * Copyright 2011-2018 The Bootstrap Authors * Copyright 2011-2018 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropup .dropdown-menu{margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::before{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:first-child) .custom-file-label::before{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-control{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-control::before{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(calc(2.25rem + 2px) - 1px * 2);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .dropup .dropdown-menu{top:auto;bottom:100%}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .dropup .dropdown-menu{top:auto;bottom:100%}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;background-color:#007bff;transition:width .6s ease}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;-webkit-clip-path:inset(50%);clip-path:inset(50%);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;-webkit-clip-path:none;clip-path:none}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-muted{color:#6c757d!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}} /*# sourceMappingURL=bootstrap.min.css.map */doit-0.36.0/doc/_static/vendor/bootstrap/js/000077500000000000000000000000001423054503100206555ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/bootstrap/js/bootstrap.min.js000066400000000000000000001374601423054503100240250ustar00rootroot00000000000000/*! * Bootstrap v4.0.0 (https://getbootstrap.com) * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right

',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); //# sourceMappingURL=bootstrap.min.js.mapdoit-0.36.0/doc/_static/vendor/font-awesome/000077500000000000000000000000001423054503100206305ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/font-awesome/css/000077500000000000000000000000001423054503100214205ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/font-awesome/css/font-awesome.min.css000066400000000000000000000744301423054503100253300ustar00rootroot00000000000000/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} doit-0.36.0/doc/_static/vendor/font-awesome/fonts/000077500000000000000000000000001423054503100217615ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/font-awesome/fonts/FontAwesome.otf000066400000000000000000004072301423054503100247300ustar00rootroot00000000000000OTTO  CFF 9s7EPAR(l0OS/22z^`cmapǢThead6hhea P$hmtxJ+t maxpP `name>$# h postx  FontAwesomeC   U6U6 22  ",04<>EGMT\_ehmqy}#)4>HT_lp{ '4=GRYfoy &,39COVcoz"/5;FPUZes}&+16<EOW_hmqv| )04=DPX\aju(,26GYhy %16;>EMUckox    $ 5 G V g l p v    & * - 0 3 6 9 < ? B F O _ c u     & 5 B Q a f m t y    ! % ) - 1 5 9 = A H L P T X \ ` d h l p t x |       % , 3 7 ; ? C G K O V Z ^ b f j n r v z ~   !%)-159=AEJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.29@GNU\cjqx  '.5<CJQX_fmt{ '.5<kvglassmusicsearchenvelopeheartstarstar_emptyuserfilmth_largethth_listokremovezoom_inzoom_outoffsignalcogtrashhomefile_alttimeroaddownload_altdownloaduploadinboxplay_circlerepeatrefreshlist_altlockflagheadphonesvolume_offvolume_downvolume_upqrcodebarcodetagtagsbookbookmarkprintcamerafontbolditalictext_heighttext_widthalign_leftalign_centeralign_rightalign_justifylistindent_leftindent_rightfacetime_videopicturepencilmap_markeradjusttinteditsharecheckmovestep_backwardfast_backwardbackwardplaypausestopforwardfast_forwardstep_forwardejectchevron_leftchevron_rightplus_signminus_signremove_signok_signquestion_signinfo_signscreenshotremove_circleok_circleban_circlearrow_leftarrow_rightarrow_uparrow_downshare_altresize_fullresize_smallexclamation_signgiftleaffireeye_openeye_closewarning_signplanecalendarrandomcommentmagnetchevron_upchevron_downretweetshopping_cartfolder_closefolder_openresize_verticalresize_horizontalbar_charttwitter_signfacebook_signcamera_retrokeycogscommentsthumbs_up_altthumbs_down_altstar_halfheart_emptysignoutlinkedin_signpushpinexternal_linksignintrophygithub_signupload_altlemonphonecheck_emptybookmark_emptyphone_signtwitterfacebookgithubunlockcredit_cardrsshddbullhornbellcertificatehand_righthand_lefthand_uphand_downcircle_arrow_leftcircle_arrow_rightcircle_arrow_upcircle_arrow_downglobewrenchtasksfilterbriefcasefullscreennotequalinfinitylessequalgrouplinkcloudbeakercutcopypaper_clipsavesign_blankreorderulolstrikethroughunderlinetablemagictruckpinterestpinterest_signgoogle_plus_signgoogle_plusmoneycaret_downcaret_upcaret_leftcaret_rightcolumnssortsort_downsort_upenvelope_altlinkedinundolegaldashboardcomment_altcomments_altboltsitemapumbrellapastelight_bulbexchangecloud_downloadcloud_uploaduser_mdstethoscopesuitcasebell_altcoffeefoodfile_text_altbuildinghospitalambulancemedkitfighter_jetbeerh_signf0fedouble_angle_leftdouble_angle_rightdouble_angle_updouble_angle_downangle_leftangle_rightangle_upangle_downdesktoplaptoptabletmobile_phonecircle_blankquote_leftquote_rightspinnercirclereplygithub_altfolder_close_altfolder_open_altexpand_altcollapse_altsmilefrownmehgamepadkeyboardflag_altflag_checkeredterminalcodereply_allstar_half_emptylocation_arrowcropcode_forkunlink_279exclamationsuperscriptsubscript_283puzzle_piecemicrophonemicrophone_offshieldcalendar_emptyfire_extinguisherrocketmaxcdnchevron_sign_leftchevron_sign_rightchevron_sign_upchevron_sign_downhtml5css3anchorunlock_altbullseyeellipsis_horizontalellipsis_vertical_303play_signticketminus_sign_altcheck_minuslevel_uplevel_downcheck_signedit_sign_312share_signcompasscollapsecollapse_top_317eurgbpusdinrjpyrubkrwbtcfilefile_textsort_by_alphabet_329sort_by_attributessort_by_attributes_altsort_by_ordersort_by_order_alt_334_335youtube_signyoutubexingxing_signyoutube_playdropboxstackexchangeinstagramflickradnf171bitbucket_signtumblrtumblr_signlong_arrow_downlong_arrow_uplong_arrow_leftlong_arrow_rightapplewindowsandroidlinuxdribbleskypefoursquaretrellofemalemalegittipsun_366archivebugvkweiborenren_372stack_exchange_374arrow_circle_alt_left_376dot_circle_alt_378vimeo_square_380plus_square_o_382_383_384_385_386_387_388_389uniF1A0f1a1_392_393f1a4_395_396_397_398_399_400f1ab_402_403_404uniF1B1_406_407_408_409_410_411_412_413_414_415_416_417_418_419uniF1C0uniF1C1_422_423_424_425_426_427_428_429_430_431_432_433_434uniF1D0uniF1D1uniF1D2_438_439uniF1D5uniF1D6uniF1D7_443_444_445_446_447_448_449uniF1E0_451_452_453_454_455_456_457_458_459_460_461_462_463_464uniF1F0_466_467f1f3_469_470_471_472_473_474_475_476f1fc_478_479_480_481_482_483_484_485_486_487_488_489_490_491_492_493_494f210_496f212_498_499_500_501_502_503_504_505_506_507_508_509venus_511_512_513_514_515_516_517_518_519_520_521_522_523_524_525_526_527_528_529_530_531_532_533_534_535_536_537_538_539_540_541_542_543_544_545_546_547_548_549_550_551_552_553_554_555_556_557_558_559_560_561_562_563_564_565_566_567_568_569f260f261_572f263_574_575_576_577_578_579_580_581_582_583_584_585_586_587_588_589_590_591_592_593_594_595_596_597_598f27euniF280uniF281_602_603_604uniF285uniF286_607_608_609_610_611_612_613_614_615_616_617_618_619_620_621_622_623_624_625_626_627_628_629uniF2A0uniF2A1uniF2A2uniF2A3uniF2A4uniF2A5uniF2A6uniF2A7uniF2A8uniF2A9uniF2AAuniF2ABuniF2ACuniF2ADuniF2AEuniF2B0uniF2B1uniF2B2uniF2B3uniF2B4uniF2B5uniF2B6uniF2B7uniF2B8uniF2B9uniF2BAuniF2BBuniF2BCuniF2BDuniF2BEuniF2C0uniF2C1uniF2C2uniF2C3uniF2C4uniF2C5uniF2C6uniF2C7uniF2C8uniF2C9uniF2CAuniF2CBuniF2CCuniF2CDuniF2CEuniF2D0uniF2D1uniF2D2uniF2D3uniF2D4uniF2D5uniF2D6uniF2D7uniF2D8uniF2D9uniF2DAuniF2DBuniF2DCuniF2DDuniF2DEuniF2E0uniF2E1uniF2E2uniF2E3uniF2E4uniF2E5uniF2E6uniF2E7_698uniF2E9uniF2EAuniF2EBuniF2ECuniF2EDuniF2EECopyright Dave Gandy 2016. All rights reserved.FontAwesome [_"+/37;TX_dhn#'Prz.26:@DHM %*.48@ENUZ^}/3PW^cgl8<FJCUajov{ @ J Z  & * . : A T m r }   ; B F L T X _ c i n s z   . 3 8 @ F K P p |  & E d m z  %1=BGNU[e #)-7=CJO]kr):PUblqv|",5:BJOTgz$6HZ]hs{  &,6@JTX`hnt| )8@OSX\bhp~"/4;?FLSW\hmt ',2=HS^elw* A T&fAV TlfPzz  P 4 ! t  q q bt& y}}y 33 % 33 `zT~~ 4] Tg@Z 4  R ,T[@ << 4 ,  ^ 2 %%%%%% 3 T< nh @ ;T N TITN C KFKk 6 ? J  : K, : y}Tj 5 / W  K$ 'T$ V L  v   L  6 f y}}yy}}yl z||z % 1  KTTY= |zKz||zKz|N !5 ! ff( G Q 3 |T|T| T T T|zs R 3& ' ' < @A G   ^ [= T / 3 c - `V } hn " Bv g OG `E}n\>lg ,hh@@h EQ P  |z@z||zTz||zz||zTz|7 F x  3C DRRD D u y  ; ;  5!J b h  5 / TT  + - tzuxu[Brlmyz~5qsU hnnhhnnh ttt  T  y}}yKy}? j 3CC  5 ;(=ZXWG/9;/_Mknmn9:YIƑP`q~d_i rcrr iii  y @H -R '   T  1<t0  lnl||}_zob^^bzM ~w~~w~ K = +tX @] @gZ t V``V; ;`L< xra YW  @3 &  ~~w~ @    5! }yvKyx}zy n   T7 rrcr ~ g hnnh YYG P ~ ******** = 4 4  )  .@ (  [  h  P v T~z$ j +[   <<5! I 4 * A C 7 r C 7 @r b ! 6g  T E ˋh 3/{V= n\n ]9 vx {zz{ X  CZ7)D T}yT8T C T7 Tr ]][ 1    7U f @ m   < ZZ ZZ { B r r z{ + T  |z nh yyrrrry pttp&pt 15 tv ' K(   ;;  g  $4 y  ~ MQ s QDnty y  t Ft e 11e BB  T  2     r   I F  y'& K w__c 4444 p] R  GTTX x ]]  83  wrr h  h@ ;fveK \xcikvss]tRat 7+447 && 7  V ( - hn  D$$D  , }t P  `=db97 Bx  t3 ?L g__gg__g a `V     C3 ~w] } y6%6- _$cX ~ TR V22VV22V   P@zyz z  s/ A v  zz{   b   z -    f t    & 3    ]] EGxZny tP P   ++P,  ʲ ,   _hmx 2       ˋ  d4  4 T[ `M`M y}}yT, V ;; 0 && T 3   t' . %  @ p ) qt{tsoy s%$   333vK  44 \ ~v  }jii C @@ x~ C Kw 5 !4 wkz|| ,$P++   -     g s} }y     f   #E T @  )Wbit  S 4X wmxyjh  ofZedZd W f r rsyy'&    h@ v   }592I88!~   I M ? y * BP|88;l]5m+\<b-G_y'>U>c R !0!","^"#0#$ $q$$%%~&5&'A'))*J++,,m,,-..1.../P/00192245q556<6717x78h9:S;x<ghhi-iij jkwl%lm7mmmn$n;nOncnnnno"oopp&p>pXqq q}rIrs8s:s<ssstuv<wIwhwxGxy yz&{6{u{|||}~~~~CM9C|28VP cSOI#|L`m Po1*x4f.HU\1'Cw[W(b;J{.ŝQƭfǮ*ʛ˗̉͌|`ϫZҝ(Jտ׻p9D9gtg,q?o]1aJC0g $   N   F.yq4+M< !>!";"h"##$b%g&D&''''''(()*"*++,?,p,-F-U4>45~566636>67 8"99:-;F;<9<='=\==>?Y@RABDEAFGH(HIImKGLLM^NZOPxQ@RS%SlSVWX:XRXXYY]YZZ[+[n[\d\]g^Y^_2_`5`aacBdd;dWdvde!ffgoghNhikj@jklmnopqhrtukvYwfxzV{r|}/~~Uu[ tJ~3J#c$;Tt TT4P 4 c z..ȮhKhh3c # ^uiƭR@2A 4 FMffMZnnw   v x P  `Vc~ofa[! Y!    T@ b@ suw#$L>$#69JX"!!`V+/EE+V1RF _r Zo p]t ksu[ztvUZ tq9 [[9:QQ:Mqksu[ztvUZ ZJ J&  & a )| s Kw t w4X ] g@ v   YT3 Y`VV``VTV`Գ  T3 YT3 TV``VT; YTV``VT; T\TV``VT;  ^y $% IVhhvjyy  IIVV V V ttC KFttFKktt r tt> @   V  FKkr @   pP tW&S:aR`S:a))6z 6)õ`a;R`W&tPQEEQQEEQY 8 &8 &T8 T&8 @ e { zK}zaEV" nmloL{yry}{{OJNll~n|i&js^^[{m~mkNo|y|rz{Kpijki\f_i]QM[!|Lz~rǑ̒Ȫ'fgiMm([popHH4 wOVVOcZwE;L1Hu v tnnt/ s~oJ,zW`aGahc~v~AHH  w !4t4tt4tt to T 4# )vTV{||||Ng|5ppTy~}y:y~Tppur5|gccn_Tz}y}}zT T dgf[wXX[fe6 tqTKTTT TTx44t8 zT~~f9x44t8(& T T9vT ,T,ThXhYm}}chhcqj}}iVgv wxrwwvtL# P  !SY ylD&)'C3$ Y4K Ti t}yT|}zcesd,.9/F- 1T5 T "Q>W "SX5z|[,9FZ3 Ti 9 "! ! T@ G vTi TT T+3 kT^^^^Tkcv ]btkr Kg _=1lno1"-SKq~n}s{x}zsz.;3n L vTTVT/WW/!(ZMj: kD L k+8V=_GxɁHKxMG_8+ MrrN-hnog? ?go Gw_ rN-hnog? ?go_QPox}yCQ(Csyrp}t{xo^PQ_K n{}|zx8 S``*S8 qxozo||{}s}|{n. K    x       0m 8 vvʪʪꪫʪ骫kihvvvijiʌ 1 w ʓ ʓ 1Y1Q kllʙ F?ijivvviijz )z _^X*DtcX_^sjii}jttjjhsW  m g|vtywxog`vf/TFw.qra\zzzaM{tswxyzzVc,sj|wut{tv\h2p]yx}xzuxWi:mY{pvzs~{sww}e_^#:/r8"   4< 4K4"Kme,,eBV4 K"44"4kt4:4t> )T33333333T4tXr=EE=UIrXt tK T/ ,Q iep%/,xxx(((#Ɏ wR'VbgfVpoqqq{\/j}}Yh^?DFG@EatV@ha%-n<5scsŔO5*VJM(0x[[_}~􊢋 %;AHW{'Qbgfg FIGf=R!Gv^]^z8'n\PuH#hPMqJK{-!ߜv`ЊxġMMN[ĐơϦԖУ!!!x$ǁΓm`r;ni~GhftnOlFKwz6- ;p6p_ph6hpo;_}oh6h6}_ Ǐ\|}Cy ^^^LuZ qmeptcCDCm  ǐ]|zb||}3mrS 667W, "m~yv}u] y]h vp|zwwzv {y{  |p hm R<0 R<P 0 R< i m R<0 R<i m 1<0 1<P 0 1< i m H H H t##@w t\ > tTdw TiFy tdv0{tz{~'&9* TT33T&:''~ )TTTn4444Tt|z@ 4kX S @g@ m  D~~UT44~sjiij}st:944::W  {   NLT_p’xJ  vPPϠHGwwsrP mXXj:bkkcv`~:jX;`Y;l-&PyyQ 4 S+,,||~KKXfccQ+4444400f,,fMff//  gt}{|y~wjX|zh "Q2{zt{tqT4 7\3ulz* p4Tqt   Jw tKK3CC  G fccQ{kkYkkkYkkkkYkBBk C     - 4= 1  gsvZvZ SZvZZZZrZhlvlr|hh|e P @g @g i e P @  ZwZZ2ZZrwhZ P Zw Z  @w}rrwrZZ   %L.2::zzzzr::2%L'2zz:::: zz ph H Z hn  e }2zz11zIIII{zzz1IIII IIII1zzz{IIII{zv P zz{zM vv,+M 1zz6 T 4y}}yTy}T T4,#Q?`\pnZtҫȧPKgjzx}wy\O~#7@TKT ttt4 4: T +y}4j 4y}}yTy}4 44 `$$`$`$ $$$`$<Tg #Zk==k##kZ==Zk#<#k==kZ##k==k#i ]&&  &&&&&&kK# g2%''%% ::!8# t  %56&{SjjQh[=<<=> >KwP ^CT}s@skiij}sstv jt }sTӸKw~ssjiik}ss@@stjtTC^OGGOTsv js@tE @wKsjiij~stsv ks@sTC^ǸTs @KT@sjiij}ttT  Ttjiij}tsA@sv jt t W @j{t,Qa! KtkvqCt e t ԛ 4 * <<< <+!y}|z |RT|y.}|yMx|zp 4HhnzhThnhTThS\V`fy~5V``VV5`V RL'HMoZd99dMH''e L( $4A 4u v߈ /J7I[^_[Z_~}yhn{x(HZf7p\XTHaG-whhiwVQZ:#vz]l`L{l{,+\^˒1 t4C FKk@r CN.ETiCkhT$T$?LL?'0cGv=< vc0;'dquuq--] LaaLvtrrtvLa`Lv$T$]D'#5'0cGv=<#7quuq-.] Sv-yU*PNO_Z~wrsrswH7*V3ziU{Qg eg SA:NT~=L=&0ErAuX5y}|y }R|yR ~|yMx|z]pkou`\\`qbuud[ddsP uz``K4K++44-3V +*QQ듔VV땓4L554K 4 ˫44˫  44Tt Tt Tt44K Gt4 tK Gq q bt."&Ft8t+ +K Qc-b.T5MKTz|sRrQnSSL0t8tĤŨTy}v0%%_Ib \;COLD|yz|rs{A0%e P T%Ki``iK%,QQ,g /g / arzyzyrrbr:9r :9k lr:9:9rrbrzy zy) 4TT@yxxy}||g T44rdTr 4g T44fTF4TTB ||} pQEEQQEEQQEEQQEEQg OH `E{l^@lg ,h v 4 4 &Q)WWXg3 UGQ {y|ss^    / T   14= 1i    m} t2o`gfbnh ./>p+>|Ri/8Crb{Zja_qV Om|  PC44T%V``V L teP  T  hP  TTTT noqqon Tft//tq:v++n+*mm*+n33Väyppv-)mvv  >{ ERQDEQc ERQDEQQE9},~ q 2srqt-}}N}}~ZTYprr~n pwefc~rrq/s~|~M}~,soppndmfnen s -}N1kmo/ ` >a B`  aNty6$7mF dI.3WW- hn fo1\s\ko{yxx<^  U/SkW ?Ÿj-@  +6 OGo Dɝ·lZ'#ik}ts')2OKebh`i_mdG1dqhWm]a"WY VF e G.3O׈- 7hn GNOH 6  t@K̬-*osr^ ?<kO篞 OY OxxytR]׈ssvkc\k}\vsO1fOzkO~rvdOJ.eY$n:moOhq1d_`cJl2)t}ǏymD׈ 83vb@KM>M>KR4)<5Mnɿ<5)4RP p]o udr T~ϧ\ ԕT33~ϧ4 J{{{J{J IYU:=YϿڼWG j8Ke`bz|vw{ ̋{&,(i"z  4t4 T, L T480QEEQQEEQ08.(y{wAi  t XTiTQg B 4DD G U DD t * ^GofTp ^Go &  8^!Y1/)Yb1+3 X ] +V``VRzf|Xm}[YKKkK+++K+>7+++k˙̚zfR[ /`obt@v'T_Gqzy Ywjo`)Ib`__`b)`~oDW~jgw^SX _~|~~tjn~@t^oYYk|P/"`c}{q_'TvQ yyt    ?ApDU88Dp?6 \xTTz{{z~TTK TT 1 !8 2 ZZ.n82Y\uZQ m{r^-Ʒ֫Ϧ [ @{wx^^]Up[c\ˀt bdee  @$fb% aa>"ipuleǞëѯ X4* (3&  &;*226;*qXsIm[FHNMo;otpлͩ&oxtt_Jdwry0Ayu{&Ay  v(TQrLyJγʣMfEpB}P7.G$%Frrs3Xo[{TO(QVY`1(mpnnvww ."4X+prq/#>VK?ʹķ Sp.v/nQ11'A<* <<xp%j]^hYE֊ׅB ?Gߩϼqٵ˟'(͔͂z'w!q=wUG7HJ?xs]C$8rwsp+qi^arʆŕ vTTTT$T4\+T /i )* l @Z @Gtt   V Tnzi.],++,]i{{}zyjpnjry''{{~{y#joicciq#4 444@ G2t1v~z1vF4YtHAAHZEtYrtpg  2  4Ttt] TgEuFF6!1=۴ n_F( RD\\D VT$4[ .G^SSG^J(@twT3fV``V}~d3fTw@t(EQT! Te `wrPNxyprNV[Pwrqqyxyprrwwr[PNrpyxxyprNP[rwwrrpyxyprrwP[VNrpyxNPrw}PNVVNPx,4.oU wtFPPFs\k{oyxx >\V?Ckwk++JLOG  =3`?.Qm\ibgbjnG5[hofuelY=  ,.Gc4n8`XC>[B natĹo8ixFPv8+֫ঽtttuV]]B1 o8[GngimQ`?34=_`b    =acfn}|}KKYXS#Ln8  4.B `KPV?Ck1B]]Vvuut+`PF`xiPta ?MQYKK}|}Pfca=    b`_=43?`Qmig`nG[  ʰ .Gc4B tZB xxyatRt]ssvikcx\j_qFPPFGOLJ++kϰkpC>[Hkfuf h[5Gjnbgbi\m.Q?`3<    p=ϰˠSLH QQ{zH 00 0, {zz{QQ   X00{zXz{0QQQQN0{z 00{QQpQQ , {z P00 M QQqQQ, {z %Q4.&E݂v'* <<< <+'~'|iyzr|x|~t}uz~}tyzrjhv~|'{|~oz|'r}spwhjhy~|}}|x}owuxzp}o~vqyv}}{oy~tcuyuu~xr}|~gwɛ|cx||v'݀t| $|~d+|~vrys~݇uw}{~|G|}}xzutl݇|~|rk|'|}y~z{|}{x|sv~vzyzzy'7}r~ww/*Gs kin 8"W==sv jt >>Gww|&xjUt=N,B[ Q?F  t{tqz4~ zv x  44B  44t tX S {e w$$ Tx Tqt{stoy$$$$tqT5 Tp $$ yots{tqT/T $$K T0 T $$) Wn|`_]#v:[vVi\\iVv6*446eTa u܎v#6]_`uuu0n1W@^;e UU`4U5TTTT`4S2SBzyrrrrybcyjdM djyddysqSUmtvwjoXV``VXojvwtnrryBddkybcyrrUnTddUA??BnUU'&UVlA?>CTddUmի3STk@<?BUbcTm,Ԩ'&)J,> KQtd_O>Kj }|},D!/G  # # @*! ! @i##flA\4v4443T3o@TMK"~xF͇F6)-1?pWSRWn?=%(EUmþBB_XS-(mU6EF(%=?VXpO򎬇F˞y\&sqb]NENewdG&NS6}dNDwO]bqNñџsSe&GF\}w~vt:4+q4CKtېE,  aV4dYztdP\4VAlff,,fflAV4< :\i?fflAV4M 4440M 4|+fLdUS55TTd..Ġ ..||eWT6LL6UVe[o!"m\à B)%h;=h&)CMe0 0 4\ 44< A 4{}~bx4T TGkmeeBV4V``VTe P  & P T wVn5!Jt4C 7 F nt4C 7 F T 7 F ') h $J7_H,  `djXg]SˈScfzhebpR3 ^v" Om(;.?GdFjPyi7voMyyy4 (!?::: @(t T @ Tz|> $@1 i{pkgGR[".__ušȟmNgG&߅ȂAP_ATeAa6226^%OLJnpsosxZWS]{`lcmcbnXzyY\a\^cbhnnpszf%_whY+W~ cv͉ΒИ15hv9U!݉}t{D$ WW2 g 5 T4g[wrrZZTTp_Civ9U:j\iCeM#&nYA ,Ómxwr .ffFfH4 ze`c`c#NW[S 9Z))));7eefeefeefee)4  {r|sv>(T+J~ff~JJ~ff~JK  g 5 /{i WԂ W~  TT{z4TT TT TT  m F84  X l @ @wWT ~ KWT @wW~ t0mjingr;<7 M7#?#77 <:fim B4@ V7)0[/1/^//106;$p#sEAA*,?m6"mpF=(G`$.ƣ 0п D&l&yPsjiel{ppmoy,,yrrUg[giyxtq]um~~~~mu]qtyxgi[gUrry,,Vompp{leijttW m b GTTG@u^9v:p%"M$%MڑhiGGTTGT&&@;$yz%:@b %% v TDddDWXYV_lw}v~v*AdDyo6$7 ^~ )?cwrvy~x]͈}|*YvvT p{3 +T TA \ < T T+ \+TT+ \+TT+  m 4 XvvuuvvHNNHHN)   1j j/ eU> k) $1   4< 4T TGK4ime,,~\-4:4 #x:4tT.F pF F 4KqHaZxuuvwtD6O'xODwuxaq\_ II_ \DD$2?? nzykjstz{ztsjmy}z{JlQeűťž̛{yn׭ 0|zT|T`> t7 `Tz|)ttF TGtt )4z}|ytT ty} b tT4TN[cG=B^60AQEEQQEAKuI7#e  #7upjj_pB: ܾئ_Wc[|a m w7Ep{ m;4U3ua[ RҢ&{ & RD[apdu- U;4mph]@@h֦ t tK} KQ$4[ (@twT3fV``V}~d3fTw@t(EQT! T)TTT K5! Khh5 t TQ  _ 4nhhnnh4nhhnnh:Bp צg U ktEQ9 w !44>TiT( ttT1 TiT1 Ti99 t"! N T|zKz||zKz|IT|zKz||zKz||zKz||zKz|6F ^P @g 5 /i  Ut"! 6D> ^ k< TA K +K G+  TGEg p\T /i )> GWW2G4 ttT15 4hZwrrZZrrwZh4 !""  " Ti  4 44 ttTtk}44 kQ)TkktK +4Kk44Tt+kkTkTsTskkTkTt44Kk4 Ftˋ v |g>DRTT˫kTTktkKh@@hTTTppqq4  ph  zZ4  k   k zZ  !ZtLLAZ4K K ztk  z YY Y LK )d { |zX ] g4KGfg 0 K )+TKx  ^4Z T] TgkF GTԀ `t4+V`@Ӷ+r S > n 4Ԁ T q] g 4d_gg_ d4 T GTT[ r  EQ9 v ԫ  TTYwNTt"RDEQRDEQbBTTBQEhEQXxC3p 3CBc BT  b&'&e pe P  @*j{4a,t {z4 tC8qbbb{y{x{K  t4 4t__4\<-7ʗ7-tD&c+zi0&H. 0,-##s& &2iGz@@RQT+c& &t  x P tV``V V`T^ T TT 4 &Q)F|~aiEjVulѬo70 XDQ^ 47mGGT4} & Tnaxjigxi j(C(jgjixhi5'=='5G8 mTi n5Y'=='5YihC ix8  Tg  0 T8 )TK4TTT: TTx TT4 [TTTTKGxP  x  yy p< ZQ ) I t+t I 4!+ I r@I 0I I p%I 0 0 +t $ h q^jM Pdioo '.X Qv \buD J  zxvuzLJ?  s ]RT1 T*)\&Y ffffzM{yz zyz  % @tJjZ!!3!" $yf+/Y kzX,Hn|}1dtZ\IێĬ TPv4TTTTT{K=but5mUzxwyysqggKgywxz{TmӨ'&h ~zUB>>CnUU'&TUmC>>CTz~{ky 75u+= TTg K%.Khnnh<KT/i vPKt/ ohhonhhn ;mg<&S3r< ;|#&%6Nkjk hW x}p;F&<U3r< Y;|$&%6Nlik hW y|p) 9Iv]Yfh{osjeV]]nw vvKuKpJQT*FhltnݖݘƎqDA5%!*QTFhulstnl_a99:Pp~݀*Pk9okթm p [ SD D DU D$$Dm8?CI9 ..9~`n [ AEN^ U TT% 7;L9\XpqTTg 5 9 $9  # 4@n T+}~|C3p knr]J'V{ke{ohc-#</&|~T+ t`` t{yS;RQPIODwt{K6KtqvMn;<-=vvkhF8 !!f ZZ3ZZg%E E !  a!f %33gZZE  Z!f f %3ZZgZZE ! Zf ZZ3gg%E X FIC?6IY(uC XVYx\b66S* PeSGQGz5:5'DN5TT(TKKT(Tn 4R~~'1 A3ZpT4 T7׷ b ,9_7T5 2 TZA1 ~'~Q1  Q1. ꗐv@Ti Tt2 @$k\9 e   yyQ a U) _ T_ T_ _ T_ T_ ss G- |a99az~z |33z}z99S e * <<< <+wvttvw_+3sE Z@@@@֋Y9ZYhYY9Z@@@@Z݋ Z  t   tR1 ~* Q 9{sYsn{xput}T4T~T7T }4TTru|utpxnurT}yZnnAf 3gggggg%E  (@WWS+}}F簰ɋf,,fMff zzq{ttz{ f %3 xt t 444G{zs{4O!mFNB9x*}}~5W]4xE G xtTc ## yussu~uvqxTzTQOy7}TxvvxzT}xqvu~OzTxqvu~ussuTTt: T,T[T}ysxEFvdyDs4>$0K_|h)!LNMMNwK=KQx<rGCTU{x%%%%TD即#}f ؋›%%%x%{TG>h  ˳&~'+'}~~}}33 +t4b4tD809mi%if+qU3@  nDDnnDDnnDDnnDDn.bXXbbXXbpc}zppqh&c&}hzqppppqzh}c&&hqppz}c o11!"!!"! $o1111o!"!!"! %11o $Z< <ps 7 7 e &]&8t#4#4-_G_G C3uXr 9*Hb=gh`̀, ް5-"MM/8(x,(90KDzіɕOTOm̀ցQ\Y5Yy{))+)jxYhmG{IUsV7=o{vu! z'f@o&d1caaPEb4"f|aunO鿦ɯ˱nnoI7J!I5.OB\WQĦdRۛ~-aOpbKI2C@lU[s^Yoc`̄ƃ~ƒΑ~vD,@aD1"@3byЀѐl"k"rbsIr3p1o1]_qewG1('$:er)n'y*ԧӥؘؒ6;2]zt[uns PDcl|P~_q<}Nx0k<N/ pti"d-"`#69VѺDMV"TAK$ Th~tT  t~~h hh ~t t{tR t~򕃘t6 ~~t ?t~ qqPV]]tסжihhMD;ZQuItI[nt]FEQZ-[+@@*e-8;@@4uvǹߤ p7ZYCYCq5( v >>>-r>->j7)1 ;auabtavzyvvzyu:uzyvvzyvLR]]SBR]ĸB]Sx*.NZwR]ĹwwR]ĹwǼ|CNGCCG|pNC!C,313, q|]RS]^RBR]Ĺ_wη}|w$䔻kiᦿůI7J+kt}n~x?z}}}b;u{{~(0YP KS{TSm{qiTAsFGKiwzw0o_ewkj "˒lshztu|Цy(0u"5@B'\ϊ؊sqٱ0@.&7e}|_g͗|qD|unlaK]~d iqqquzw|wʎó^=~Şv}M,7QupzTS(pzKYNGJ b/ѓcctup4K6gp1zy@yr7Y}{w\wxFis}txyoGqt sp^)X)iz=JFdf|oL{1$+#~[G0`SQRne*wXjsIx[Ͽ^d7,vX9 ZY 2deҦt0 tE EE#)vo}}4u{zu\ O#nWvZh,lt:$4Zsj{rglb1XldvG'bQ^{yqa|x|{jjs}.Ӣѡ?IY–Kk.#4)sV 1|5Gc%1A XRf 7n]Mw]^}ǟxwVo] ytyywyB A'!3EMM!#]([B4WtIm@nxWxWtIWȇ$rzӎlQ3J>Rq_(%vv==)G/H{uAR6=z@kwlkkwllaelj{RI7 A5ifsgffsh./gge0lF  miE#=[Z\Z#=EOiNQ@QyQ@QpzE&}9ً܉{H[1N[GCJۋz"q*g2EKa"81&*a/rwxrrwT(v]*I0 330H5 7 Tz|4# 5 T|6 T> 4# T3~~TzwwvxT44Oe9 1 dpSF47zw8,lr7RZ(x[ts[{+;fC3DK^Fxukrlqv}TK ?(&PX+)#JU ^mmm jgenyiYW»ëP7iSպԤÎ˒rSppoG.B%r u`vtTtp TR4%Zdz{ IS4( k. pkTt t  tRMo  6 ~* Q)ۛS%4՘ΖT˫KT]HAF-"K g_yz}>Q~{{~؉؇}zy_gK飳ܩn_ZZp_bn:vkbA*t%ndʋ̫44m4tbm++44kkLJJl.d |{|8S"1ÞH=|}}6TV5wSLTT=g}}RIcZYccYZccYZccYZc \pcdwywxRj.j.Rcoͭ}t qZbZZcbYZc\L gSVIm 0ܰ .G.k.L.k?+llH\\HlZ釧鏼0  kcthjz{{z7LvvK7isùĨwлQahaahhaai?Ul[Ĺ]SZ Z+)**MOvrqvvq 25 3 + qv6!Mr342oqv*  ).```NW{WM}|XLyR]^SS]TTVQ~ùù]SS]^SS]WQURTTtt4''tTTttTTtT 788a`aa`a^Mkl` 8aMakaW`a9M97Ba 8M97Ba Hgg[o\@\CG%:`dhbgbۏ֯Ȱ:%G?G%;adhbgbN;%GH v/}7 Q Yr3FZXaXxwx_blkxB) K%Lo3BJwu~kuxu*k?Oz!xyxvzAY Ϲ[Djmhl|{{̡ԡԈ֊ j8ч5T&9E Z$jb<r(B{]<6TYuZ|iJC^E,g_zsyubՖӪu^q-1ݛzJ1jI1jgTiԻEY}MF{M`@]~tvtz,J ~Y=U/0Aqtתԛdz}PPxvtnos~}mzVz-cObPru[N S=)id<&liXsŒՍ0ZZ6:3W4U_U266WBN h[aj6GUv@cLj^HI,+Tjk(jjc+,54+,mmZZ;ZZ۽+,33m0vH9*/o⩩+,44>4 q{7$//)9wh 0m+,54+,44,,nZܼۋZ,,>'l4n,,44,,44,,mZ;ZZZZ;Z+,/o-D/#5>'}n00nm,,54,,44,,ۋZZ;ZZ+,j+ JѲ"^ z}i{ѧ錐zss^myzSvnnU{uuwz~˜ڦLvewe :rnwt]R{ϝȹ̯\jtazm|}l~~nh~uN?MamJ}fg^%llI%uXBlznxj|Z6{&1~\NULܿI4'6kZ6nNwatTTTt  T] gZ `77lf,,fAV4 gKW?tqEEE44   @ 5 /0 x AAK TV 1CK TAK TA_K y}}yKy}}yT9 ;9 >0y}}yKy}}yT9 PttpfeOefxxxxeOeffeOeDD8|  Z *`7Q `6w% Y4W%*% X4j%1g6` Q7*` D4 Y%*&4 X%W T#EE#\[^hnT^\z.}TNNNiYT}||||}TYyi[U\`uTTv + @ 8TjMQMQMQWm[FN$l\TT{zzzz{TT\vl]X$FN\vl]X4[^vTt Tt P8jJ2RQkVo8>A , '>&&2uQeGWn!eq=s)b?ɽVWX/c@o E`(yk2@ /@OlmCLAAls  e1U**j*.Nz+8a{z{aY%#y=<==<=<<*```^+LPzlX1Az/-6D&@I`_4| 4B   44!3}|~jk/k;j:/d;jkjL`* `h uY54Y\55\Z56\~  q@-33T&kvvXwpD>m2W._Z8nE 5<hLhLQRSu'/>0Agz8(ҒӑP0KC'ZL{o_uOn ɋ#xW{D ߥpBdȋeE)p3 +57wp ntTy @ y 4'   u n  t' T  K K V ' tXt@ Xtv&'y&'Y   Y&' y&' bKHJjp̃Έbi aouwr~'89{={mx<*e>okjqpi{AR*7}xE|}jp]VY0-|xpaime{}ld""p*}|blv\&A}xfa) Wpo"_m3m"3sٝϞ¿8~~}~hs׌ $zctc^_Pvv~w~yf{h{  Z~}}}}}||{|Z} z}{10df}itj\KMuTuzy~00&    <!! !< jvt Ǒml!4CPWhЌnj|vw||ryIs7h3^1c:gJlXJU>]wD&_nr6Hgdalor@/K&``m}", y@}z~|{@Ë)ҧ̞ȭBO`)y)o3hm^Zx  :w !4i 1J{z~v$${z~J1 E8)3y{||y38)E 1V!4gVQG ?33A HWT! 5|x$5! ~; ~V!4t/|h7S.1l~gd`;!wgvph  i ?v x V!4mTTT  x - VP P P PfAV TlfPzz !  P 44rn<B@(vMzzy:((! u6B@!eDRĨnhhRnD  x K!4Bf~:;8:; 5E}}oۮhJto%]8%{~yxg(|{~rxjrqO>99l>SO~~zz x K!4 EQy  1  %v w !44v{v}JJ}X}w}vw}Xe}w}JJ}wevaʁӎyLzzyӈzz|cyuYaaff6&̶Q HyA~`Dޟ(#PgQ+<3%!!!S|BDMhߺ ђO .-.-.plHs-U7sH<JJJ?H&Urs&l~v~||||~v}~rrrr}|"d ^)[OK0-npq D:)rJ?t~rIFo9$"%9/iüIIR^rdclmkԧ2*:8)0\pFN[BA\ŸghgGDDl)3=  0 jRVVVTPQSyVVV:RjVVyVTPQSVxVVyÁ•VVRjhh @xmŁyVVjRttttF4xPp[px4MFqqqqIwwv|yx*|8 G}AIrw-u\? 5'px$ PY84I5K G3#T1!I%>HGUB& v\wͷ- -%bbd&-JRprvQiu,t~՗Ӣ9RMgĬx{}ާEvhrjplJ- ?&n 5dbbI,ueui`M6fXPljiijlPXlf`M6`ZiRuL};pommop|;LRZM6`m[ [ƗM6Ġ|}+vjS&zzgMRjhed9oICAA~CtIo}d{fxgj ;v+Iz5&o? mjhĬ7;jjjjjjjj 0KBH \O+:xOa_UTS˄BgftXRweWWk!:{z{zzy"J<%wly}jhw|m'!+\! ոϡnMbx7tttpopyjef{m~ Ǻ iii yzyWuf^ V]g`[[f_\ A^N?I`Ujf#b'^jTm4=yBF$3P:kS43g߫ޯG@pFAw@UMMM%O&iWtLXU_ogBF bWR?d/y(#-:=;ra``^_^rrukedA~R?‰“wnmm"?+RU`B=jȕwS<;QE>?F`  RXX4! 55 pqsa_^U^HKʲJpwm7ųu~//:q~iykkkkkkgfhopypoo n0 +(\fmjő¡CB{gtzldg{S)ik-z/Rɮ٫ސq,Þ2=5qnYVL9+3zZ$;;#}MwzVqzvy^oyzzv!UggTUT¯¯gT{fggTgg¯ggUggUTUgTffgUgggg!Mm#[8ICnyy|죢Ԟ[TI&%7O ~~Tvtsrv61psYM O wpv~Tv~tsrvlUXqsk %]rKwx ܿ D&l&yP >T / Ua  3^bXk>C_}g555333g}cm6ﳽmv%f~~O~~~^a}g767/./h~bn1lp.[Rh5kuZi/4oe ^Wf7h7jvWi'2nfz#zCpisL2r@;pEVP<Q;Odlw #> ƭ tt4tt ttTtte 7>jVRH %HVjE##EE EE#HR>7E#E##EجHE##EE##EE E?>?  ++ +  heXuS + þuh  + ++ SXe  %  +  o9˫49/ː/4Gj{fj}^11^rt|qj|$$J|jBGrbrrKK& j SˤreGe~1~w~~w~1G zz0v~0BKR+%+ ~ T+u+~w~10G 4 v2 dd0 mcFr@:}77:@ڳm-T0>2tM2V33V2Y&Lt>T0-V- KKKKKKKKTtY TY TY "6 gn~Y TY TY "@6  H~HfT Tgnp(pT<}~}<4TT~}<2 <p4tp Y:YY%$~~$%YY:YZ$%**44ool8II8oo44**%$Zu* + uHd8lhTwwvym\_u5^/7hVfJC22C=+JVhX[<*N?Y3: ]#"S:Y3N%%I%%%%F%"F%F%"mmm%@5z"mmmmFF VmyjjgwrPE]}~Su8ӗ)xm6 |uw}un]~')kp{u~ y nkuptogo>4y}Ϧ)Q4  gyr=7TRyytvz3*WJttx~8tA&ysjmm}՗ : Vx  dTT- 5P x`  eepZp %$ B((BP! (''$$ GG(GGs$$zhl?9%$ _{_{ V| m % GGGGGGEQQEEQQEG-   %  EQt lT^_|_j-Z7BG:?)_s:y8CXccs{~syyyyzyoto֎~@,="H(`dine|Anq˗NJܨ,+Pמ M q{mv=m RJCww7cl!w/|)q% QXeYGDW[r0Id?qov|wvNX^UZl-ƒs|eW"}fڋ\GQ+L~bGDCd5.26J:#:t|mcUJmopmvn]TB4B@Dd$rvJL88˿}~=.|Նs=xzo<Bd l8l \ʨʩܧxxӪѩ̉Шܧ̩ۨ~+Un/nT8)m'xxxx+UmTs_^^_xxxx((8m0nyfzz~|~wL@ %"sx@{s@q|xs@mw~x ~x _sx{rr|xs@sx@zs (r{xs 2C % $ %w x}s@nww~s@m ({|vk6~}xenHG)qtyduw{z~l{s{y{si|hobp[N@~ r@na&s u}{xw cn3ow~u?m{D@}s~szzqqzs}}w_oG~}xemHGox~u?n{ ~}wfmG( % $ %d ;zu1t&A"Ω̵i&L̔+@~tvqazp@ubw&8@SJZu\ek pkfY8@,F):J\^GZgmn|~~rNvƯri_z{wow{vy}psV1}nso(>}>ptlN[XKH[ͨ>- "?4'::''::',Ah"ttLR AS1 Sc1JֶgLXpjZ@  =PBBPOA AOgŬ\]W»[Z3)Sff@_±RD=̹|ͻMV˹$QG̟^ͫw_BG.O`IΤwϓRϺTEDjX 3""7<"ߊmQ1@@ryt8spvu:@rfqvu:d@r_F/D@>L6L! @+Ohhhh[N@c@92;@1  y,((,mm<}nik<ytgn(Bnlmva2gWTP Q~awez!uP!Eu :PBBPNBG=_!!@1@Z*y@u@_@M_HEQJiQwrSrNG2JtA(@w\EQx]nrTrN:BNcTX.E!]^ggTW-|cQ f P+((+ll>vII<5 YaqT;Qvy{Qŷ`bZ Y`qT;Rwz{Q9RIPP(*/inW|ϊL/b\04]O__@P( e,l,|} v)eiyz ) v??4 For^{g=Zi *>薚=v*09H3 Fos^{f=ZPi E(J-I4i^xz xd 623~**)(!= xetpqRtJ͉yiiylHXzdipsl_~UGJhsxyW9Yӿwyk]]s}zw~{mh7k>Yi{ ztdYgrn|oM򹓝gptx*kSk*kl1wGb_]aTfvst+*r\zheN;h_hg_@_hh_ilu~~$rfP|KEUfav,ɼuaaP@d XvvT@A!!KTy  ~@7뀙v~6 D  T4[ *<씒>? DB(DD(BzD)ANZȼxȼXN=v :jTRR;;PPQ2<;5 ,$()MU]()++\TMqqz΂34ypm1 .oQke^ 'uv& ^ek7|epex#B9o=9B]:#/ݠp"\&"iRexp hB RXD@ _* pX@RpchE:dM+A * [ $0bwqf]]]U9pttp.ptqtoqJN JAN AJN lL6HpAOYKI++srs I0"/rIHqqIHrv;(hqjuqiF ﷰg(7]F$ g)XP p'7)28]8j*%jjjAo]" -]" -]" .\#<]i|i|i|j|666Fٯ6UaZg<xgw;A A@ !A@!!@A! @9ŵwvmQuLflD^A94wHMZXaǂݏ,!|) *) )* )* ** )* *) *) )[OCPZ[P55ZPCO[[PP[(ǻ .S"0@:M`edeSO[/:~~|yw{ >g7.iczdfptð+&4R l^vQBR{xxgd~y</Rc4jc'^d K4T+TDqT1 aa 1qӌ $ӊ4FlGuj/>cvYXwrlO[MOO[Olr}twXPYv@c>kPS/k_`bjpN1kI|  FkuU)4S'-{:d@G &zzEwvlmulchr]t bFs^[XV[ vN;mЋD1 g%/O,kjF?j}yykD D'wsxx tD D7U 1 t_>hjthhjbgSgT.  T譁bjh>n_KD;D h/4+| ZsuS&yprxoh nrR/Eoqwn썍c@ ";;dxpXBB}tq۽w Tdmlsur|jto tt ttDtE [ S)\<D 4x / m  8{uNvQ*33Q~Fu{uv+NR-XvD ^ vXRX/ m *6xllsvr}jXm 9 )[=)R~[~w~5-!i5 D "~v_V=)[D Xwauqu f!D! }qquur|jto tt ttD ttDtì|r6Z<:S vg$gJAv<ֽYi}=af44 )x5Mk4444+   T T T  {{{tT aT TF7  Tr J tTsR@6{@),\,)@əEQZT aIIs~xxx{?+)])+@8s~v T@P Tx { T Ti P TTTTT//TTTlvT T T!5  s^vt 4~44 }}{ptrmg}e}Mpvwyۏ P Xtj\b'djгg[L״( ¯#wmݿbtG(r|kj>Slst =Sls_tiqnv„Ņ3I`QnN^DyagTQ3I`nȜn;((5;!!6ryqhn}.d9=k%)}|||{zvvuyyvvx}̖ҹ֐acrppxswzn}{wvvӍ⟳͂pTlZxeyR{0o|WbeVHquO z|n*)j4_SnNe]_\]“†gwkrmnnny{ʼnZlvTdp@JI4X^xԉ@jwvw@~ny yorpmue{`nYnpr@^rss~~xvvyv}y@uwzD8{{|z|{}{x}~yz~~} ; $ˉˬ7y88Siˎ; D8hLp` d|jK='t<  jmP P Z-mm  me f44-4LLmxzzMMzzV``V43 -ժLL\UI (fc}sm- -yisnK8 A*, gtx^L p&{'%%{pVJ9$5EE$ݑͥ}r:CW*[_?P`X=}[Abo/ kk(22I2(UJU2kd +Ԁ GX ] gZ X ] g44 G4 t TTTipE77EV@p1 U_xo H 6 f  I{ _gg_n 7bn 7b$ Xrzspps^?``^ $ _`bZ[z[;Z$ &jWWj&[@"UzUU$ XrzsppskG+P Tv@T K xILLIILLIx^ mK+ tttt΄PHt ZVt ˻WLqqrHrqqr- hnnu~t t˻WL@mA ˻WL.t JMs^\tlji!) tIK^0tH!wwxt^B<``uft`WU4 x tttt* ɽYM$ ɽYM ɽYMwwx?)^cj]Dces ҳxk.a ɽYM$ 18X:b}}} FhXa"! D( }}}b81mo9ttʓ 0 %nllb !+fzx!p|/7chhk+^[THP}.}{{MYɷ7o_qccy4{H]ȣǦɽYM|%npzdcZ}!DR߼ \Zjћ|0!߼XEdkdNYTMNX 0PcXR}6~YVWt   S kIJX%Ba8k#E b>[=:-  Yvk2 OT\0OUƀԫafob~hSwk9&&FD[ _Jͽ "QQO;2x) +?q$@8q+*;u~-%xxxquutgfh%'E̹VL*v˺VM`wzTE`ubiqzuppJ_e'%|ŕ}rɾɿdZ,ɻ˾gY%8>4 x [;kttTtTl* MCΫ MC1 ˻WLN͜f´[VmJJ{K/oqwnbces՜ ѵvj+^ d^]  blՔm)xyx^HCii}l\NJp" #kk՜͜k眫 tt: oVtt c7/{{{b zf+!b lln% 0 ǓEt^+jffllɽYM{{}.}PHT/7qͻ]H{4cycq_MYɛǦsjZ\ !}Zcdznp% 0DRɹXNMTYNdkdEXÿ!0WV~Y6R}XcP^vHZt|z( z] }z!~q{yzp~"{}~{=UP? Q={~G 4IHtZ]41YW36ЧubQEd]"( T$7/V,'t 044 '%YT8l 9|2'8  (%XU7l 9|3'940 k@tt++UUttttC<<4444 TTUUttttC++<<44 aJZZZZ44cTSc++TS33ZZ 0gQvOy#DORKPKaXWaaXWaaWWabWWa 45! 54! 54!-..-......-..-..Xcc@cccccc0ɂь8`a@aNdC9sbccc@ccbcX9XE-JF,bH5@Y&nË49HbF8s̷pzSzN{R{< `_`_`_`_9''''pqD-A&aa-D`qX_1`AMtCC%&*)GGbbIc~c͋%)Gc͋cBս_m 3PD33DD33DjKgl:VF_-zMWSRn\nnnn\nZECSSnn\nnӾN+F:g˝VC&&ӋlgZG%%GG%%GG%%GG%%GP8 XP * 6DD6srpsG4Tmmv)t~̩vVJk}ltu(vumm4[ `$O?$d``zw~y8Mva\tiN߶܎`4s~nkA["gwdLaG$lΥэ`v~{ҊꅮK-5%L .U |肙i54_:vxH|Q̋yPBCĻ*O8Qz }y2!v w 2!dx%%uouyf"2E"ciP8+ H>VV> 8PicE0}D8F?:/5mV?_@)*_AUm֡F8~4pw:{hI K6AOR||| 7::-R=6jsnVK {Qr2w..4$<1Unk;HKTC$[EEq;rI/(6F+G-7+`=(c2F]U=PO>Ulx x { T@q] Tgt1 it c* KWWKKW#1bnZyOL/õB+ h'X=-k7yS[rWmK|CO]ew,i@RF˿WKJvf|)}xyk~JJ?X7gf3.x,+.46?JGXjkځLJvl~doJLN*4A@Pbaul~xyJJJww}IIJÐN~LkR{mmaUULEC><;;{mDRs*MSPwms}wy<u|ƌ  Y10 Y 1122X48C1£dzʧr]^NJqZoe~|Z~W43XV32X+1fIHJLh67:gfs˂uncor~EMUUaml|ؚѩʵɩӛlG@/' "vg9~{~iyenaMxx,wMw^Fyl}l 66xtm|cw&LL+dtjiJ4qqߠ˚|O)xOpYpZ,,pWwT,JyIE7wt4vQ^6_4 TT4 P q q bty % %  @tTtL tto tt ttD4 TT 4 t#4 +PPPP]w~PPPP# QPQPPQPQk#4 ]w~PPpp# iP# T /0   x `tT_`b##b`_ yyQ t33 V22VL ' !!yrr11K- /H !!e+TTTrryy!!!!V@;vyuw{{{s{sqvwtzz zm m{`mm  F> v m m m %%WBS qg@\LP{|@)҅%V` BBB %%  @w\h<;v-;ݯ].Sg9GFXVi_d:ftl\mM>U:\!-B o-Bvˌ{bє~8w0R8#0F2SXtegJ]lA9HH8QXi[syxy\HN-fXR22Tt4t9lF9y4d;1?;UҒ/tt<%%< "SKj<5eZ>:$ $$=:Z>ejS m$54444q(f?f?(q***M**wI9(9II9(9I*' wdA467MR*M8=IF[-2 ~ 0XPdow4 4b^hvii!r(u)-vjzfj~qsoqz az qHoCwqs|qjz)(!ivi#o#vi%GT;;;;;;P 0VviWPVgumlwa||q{cYili`dH__·I5)4y)Y>BݮU11V1h8SM$wh#hAQWƇ¹ÐvZ];();;)(<&U11UԡϱB.` eSGtCm$t]$ttR4'4$BL8,L9}x9`Q^fxOoDk3dGԴ"TUa  vsXQF55EE65E<xiUNgs{f<ϖҖ~hr84W{mXx|cqJ_s'*0󍂎iZӵԵYn5U+"C~?ihyvxnnn6 >#A0W9 |xuzlMx|lT(x||xx|T*0(=`hZ6, L86- I86, _ -i1Yn|||jjk8dJ!E+z$99$+!8YisV>crbxy~vz\\\}v{~^ws8~64468w^~ybrc>s3%p[rrcrrkii~kssqrbrIIV*B+$$c+CB~II%%Uaa;U%% 5??H5 ~)ԫ   Tt t ` 5!J Jn J wNk+mTTT11W E VNMDH>>35b d- E. :V22VV22Vd'cU/ ocuovocv?% q|~fFF 0.*ocvnvocv&0q|KTi KTTdhjw{i^u_oYlnus o^iwhY_u{jhwuji>qw[GA?ijkŒ[V(1g=VijNbi6%Qtb(e nzfl4u~d p "v[~}dtVldvtşW4tt }}} "CVt[Rt}~[tvR[Cljh\^a\Qwt\s[Rsjz\oUwQVf[VT[gdk\c`^ebiObt6Qb(Te |TT&&'dq:=2G<LCYRhn@K5 $c{{q{<<{{qz{cc00E3'ҥ}}{PRHLbyz{*Tb#E.ᕖz<_d_:@sgD_^_*dJA Bɴ׵to'W4pwοšvWXM uY1A3g,{ հ yL Z=xsav^|ZZ[k.k/k.hemf$6k+ː]V$Iqo~QNڈ7.;ghhVND<{B3^w/ۍֵ5b LQ*GJW}ϗѝڏy kTUnn~ryj_MlNy|bmmT]bj[X]~-u[_k[TZKNW]dWT]TkYacYUaö1r&>kfbr(ywvE^nw"'hRcl`jObenfQaWT`]Tti\~{&XU3tOZq]s~Q[8qQ]sZq{N\[kP`m]lM_|r(nhd&>lecovDd$H6}z~t1YbSu\qP y,:|`B/ O~TaKJ~Lyy1Vwy:pI>/2ndgaWrsw  =!Y 2R?2"?=. O?U<AD$(-}}}[ׯMgYST**lSmMrWJB`enū)c1&E,u|ѰڡšXMN9* T(T33T&V ]!zltahqrp!s,o"prrgl!| N#l[heeezpK^7Lw} cb* [)F3RtZ>UKLLdllh]rd.^7|}}\".% _qqoy~yGAokzAïwPW ~|ԪAyoqq0[]yZigmpgːt@6)HW\!zktaiqqq s,o!qrqhk!{ N#k[iedezpK^7Kx}p    #` RN{M{ xiwi9#GsE}Tl+{X|V}2h8pihE[&Yr!hgRoq-εߝA UǾԾ*}t~xqi#E3lgc2Fj`\Y=urrqrqs $>tY~\w`rk2FclgglbE3jr`w\~Yu<rqqqrr %tmZR{QsstN]wiggXnϤOTͮmwmwmwŷ v|iI#&bSS!mvvu  D4P 3i-kbUST345IIIyq߬ ) )) 19j4 :P* C3+K}T -M ujmatvhj_|[t>4a@kk[|^4z8qFl;7MtH $v"?-=xGnS-ub7È A wc-Ef7-^DNfrx~58~O  K(JD;;JEYFkT«FYEA# ## #$ ## $R+7TS+V T}y ` $ ## $# ## #[pdIH[<+N  >t:OO  %%%&$'b %%$'#( Gg4(K K4(Gg.S1>P;;>S1. F ڨzz'.<!bV[b-;PP-;b[V!E$@hh ? +YZ< [ ZY+  d 0dA1]ZInBBIZ1dA05K.$$%&T `X +S @gT T B[PP>P[,cs£,P t K: y}j Ky}}yTy} K K[R +X @l  9tk,ccTsNNTck,BP>ƻPn4RT1 T7}y1 T 8 K ' T1 T @ {X @l  o0v@0<;u^'\ =*S<,cXP*"c`[h3\5jj5< UgF19PREij+#hd$єZۯӄhw*K (Y&ZvV^e1.j4E9""оqrQ)`j#KE[|z0Y`7~?g drlf.kg*{WrrZ^mwvl[s njbgnzhylqfSB[< .ShtviKkTpxvnmn`j|kfZ_FnʬҞgn|rRMHh,Irqprg^sAM/)8[PD0nf ivqXѵ+DD yZnbt9#tx 3 us{q[Ƣ᳚s~N\ 0H" H" H" H" 1  VKTT6 64" E#E EP/"@Z<[@E EE#[<:Z TTT@TTx T) 9 __&X.$ Kp_A;__9~2M@nh M* TKMT @nh M(@nh M P M@  @ i @ TT&DNuye }  Vҽ T} ljllR  ~ufH7NuuaauuTAMr\JC+1 7}yy}}yy}K}yy}}y+ , @j vjIIJ# %BzϜԝ̒<LRosxzce%k$ld!|{{tuv\| k~}lr>++6YF$&E{YIv['?Eljo~ (9 0tX @] T +X T] S TgTZ c k~~w~}}}}~~w~&&~}}}}~&&~}}}}~&&~w~~}}}}~w~~k =====&& ==== &&====&&====&&} ""4 0_}2/bw_1*S@ H2spoȫg zz9A{ezp{{hh{zqz@ez@zz'L`FF=1<1#WX ]vvL;3lK@+@LV +< qU]%FO^ePVn`r'sysfX\Rq8dsضsF%KٮJ}3хbslt[`Tvuai4.:e?2%ٰۂ~%~GbRSlg}^`st`an좾]4jމQ@.&%&%&%&% YJ I8/-pR %szw8&%Bpq2zr% !!2.l?A  R჋Ίt,ә$j"u/}s-|ZH8 H.w_puj}$xll`?^ l2BAAq3l۴l8okmYQSBjtvml .ڎ\KHqX @l  )uAp6wYl4~yUѯW?QYm}ptjhFEPx3|(/ŏ45&ϻąfamJ?jԋ<#>n,iIΙJ\DHհ4.4g"]vzuyia=sA|M5#Mo`Ba,l#:PhCt"]уHą3B̖ڒ/qu=hUUv@i/ ::hUyrw>ggjk>pDLUxzxzxzxz[a7MzR?ݙob4~{z+4+4+4+4+ T@   (     ` @{3 @< A P    P P  x 7ޜr8{M11Mז@;NyydmyN!4p<%0d*TKjjT*6Ld0%pb;4NmdylymczN ;j|@118rz;jN mzcmmNT4o;b0dSL6jcjKSd0<oT4N!ymN ]]zϞҞԝxxy5___Yt ~rl3/r5l3/  HrvZjG ?C-T%%\:RR:R_DDDDDDDD?)|8oCA /-$",B5|6-GcKdԛӘ py||u~w'~rb F o    & , @ e s {      - : K U c g l u |   / O V Z ` j o u {  &Dbs~-18>EKOTm  ).7<DQYns%:@Ui{ "',1CUZl~$(-2BHP`gku{ $,2APWZ_drw} +8EOU[binty~ $).38CNY_doz <<C KFKkr =oYB; E#E EP/" @Z<[ E EE#[<:Z@ TTT TTxTBt) P t* i@EXXE+y}}yK . +EXXE P  +  .  '  33 y] hnnh }y ]]]]s4  . \2A y} B  ,  FTk BBa U) y}}yKy}}yTN %  = hnnhhnq F  A [ ]]E }t ""  M - \      1<0 0 +   ff }y f _" - .  hnnh C p  }yT(  - V` C3 }yTy}}yT c  |z S +o + D+ \T2TA  ʆiimdod $@~ Kz&w{yyw}| |}xz{wa&zK $|' [[ !!  oZ S 1  0 [R YWffG ffU ] @g w TH U  3CC3 X K] + > TY  [ RDh  1  TTTG_^X*D4 4D*Y_`tW ! '''e   TT  TT i hh    O  g B 4TT=TT y}7 !x! hD  : ,  JJ 4   F/B NPuc]T! >9U G @V``V}~d3fTw@t(suwN5~w}+}PV  {zg  : T TR ] Ky}  H g TB 4$$G U$$ V``VV` `V  ! _Ib \;COLD|yz|ru{A0%{[k @h hn z|  r  4~~4`_`R`e9C/R&ažҦ4A'")~4 Uff ,u !55! = = T- /T {z GCC8=<<8CGC   VV  z| YY .:t: } &&T O  ' LfeNzyz# u"=1?u՗ ff YY Yff  & /]]1a }. M MY;/a3 :  t@y t gZ !5  \|\?ZEԅc*y^H(ym|[ n U t3 ZZr EQQEEQc '> 0 4 Z V``V  ~   zz3') {z4  +<<<< { .= =  C 9"TM5Ř{~~D;i ffW 4x !x! QE 8 8 8 x  i     tkRE; V``V< A 5 /  RT &    + ,  3    O >  zr^``^?*<씒   @(  S t }    X @] -   }y yrrrryy  ( yy ~w~~     [  tt |z@( Tz| }D}} RD ,l"7o''$ { 1 D     !K z{ 8T(A(A(A mt Kxxtw~ ̍t|~}: @w{tsoyx ~  R1 7 OIIgX!!gXg! fz\J$9:lA ~w]]w~ z  mm))mm)  { G YU 3- t a h N0  K m + )v P  \ t44  < A ,, , ,  T  b  < TA  F  }t.+ݭ { `T33 V @  tk rcrr   @  >  xyots{ SK(   tp  yy :  7 D$$D   nh &T* 5 @$$@! quuqqu;;uq f K< p   QE t ' rrcr 2 T STdJ,]շ49 arwwvyr/ (DB%$AΌ %  `uttu~w ccl qt= hF B 44    V s V   Ez*6z*E! !$DD$    7T  -  R  5 yy C .  @h  <<<<  o7 TT_Ld haahi`ah  /ti V H  ԫԫ  ] Tt . ;; <:Z:  3}|A  P  3C ɽ Nba] 0 $7 o"7l      @      << y}|z  i  z{ RD 3 $$ DR  ip w   EQy 1 0 fM@ jmq  ,4[  @ XtxmihbW_)  RK( t :z{ z} R%  7 tC ( t  z x x !5  x  & %y}  _gg_    *10 ˒ h   `V  m    [  ˋˋˋˋˋ 7ߋ7   K [ L z   - -      Ty  !5  z + &m/ %t~ -   \$"WT * nh ttt v    ##   h33spyrs@  "" x@8 !"""`>N^fin~'(.>N^n~>N^n~ !"""`!@P`gjp ()0@P`p!@P`p\QA0ޕR     v^  %|_<O<01h  pv_]yn2@zZ@55 ZZ@,_@f@ @(@@@- MM- MM@@@ -b   5-8@ D@,*@  m)@@   ' D9>dU*#    R     @ e  %RE    $ k(D'  % %  0$.$P/ /: /K /Q]    ^ U k "y U $ U  a y * <Copyright Dave Gandy 2016. All rights reserved.FontAwesomeFONTLAB:OTFEXPORTVersion 4.7.0 2016Please refer to the Copyright section for the font trademark attribution notices.Fort AwesomeDave Gandyhttp://fontawesome.iohttp://fontawesome.io/license/Copyright Dave Gandy 2016. All rights reserved.FontAwesomeRegularFONTLAB:OTFEXPORTVersion 4.7.0 2016Please refer to the Copyright section for the font trademark attribution notices.Fort AwesomeDave Gandyhttp://fontawesome.iohttp://fontawesome.io/license/doit-0.36.0/doc/_static/vendor/font-awesome/fonts/fontawesome-webfont.eot000066400000000000000000005035561423054503100265010ustar00rootroot00000000000000nLPYxϐFontAwesomeRegular$Version 4.7.0 2016FontAwesome PFFTMkGGDEFp OS/22z@X`cmap : gasphglyfMLhead-6hhea $hmtxEy loca\ maxp,8 name㗋ghpostkuːxY_< 3232  '@i33spyrs@  pU]yn2@ zZ@55 zZZ@,_@s@ @(@@@- MM- MM@@@ -`b $ 648""""""@ D@ ,,@  m)@@   ' D9>dY* '    T     @ f %RE    $!k(D'  % %  0%/&p@0 !"""`>N^n~.>N^n~>N^n~ !"""`!@P`p 0@P`p!@P`p\XSB1ݬ        ,,,,,,,,,,,,,tLT$l x T ( dl,4dpH$d,t( !"0# $,$&D'()T**,,-.@./`/00123d4445 556 6\67H78 8`89L9:h:;<>?h?@H@A0ABXBCdCDLDEFG0GHIJ8KLMdN,NNOP`PQ4QR RlS,ST`U0WXZ[@[\<\]^(^_`pb,bddePefg`giLijDk klm@n,oLpqrsxttuD{`||}}~Hl@lH T H`@$\XDTXDP,8d\Hx tXpdxt@ Œ\ ļŸƔ0dʨˀ͔xϰЌ,ш҈ ӌ8,՜`lHش`Tڸ۔@lބ߬lp 4X$l( ` d      ,,8(Xx|T@| !"x##l$$'h(*L,T.L1t1230345t6T7$8 9H::;<<?X@ABCDEHFHGpHHIxJ JKLMN@P@QRSDT ULV`VWXX4XZZ[d[\|]^`aHabcXdetfhghi\jxnp@svwxyz{h|}}\lt4t88LT|| 4xLX(  @lt$xLL HĠT(  ʈˠϔldPՄxpڬTT ވL <H$l4 Pl ,xp,xt d 44,hP 4   4<,,408$8T |!h"$L%0&H'()*0*+,.$.012@234t5$69 ::; ;<(<=4?@ACDFH`HILLLLLLLLLLLLLLLLp7!!!@pp p]!2#!"&463!&54>3!2+@&&&&@+$(($F#+&4&&4&x+#+".4>32".4>32467632DhgZghDDhg-iWDhgZghDDhg-iW&@ (8 2N++NdN+';2N++NdN+'3 8!  #"'#"$&6$ rL46$܏ooo|W%r4L&V|oooܳ%=M%+".'&%&'3!26<.#!";2>767>7#!"&5463!2 %3@m00m@3%    @ :"7..7":6]^B@B^^BB^ $΄+0110+$ (   t1%%1+`B^^B@B^^"'.54632>324 #LoP$$Po>Z$_dC+I@$$@I+"#"'%#"&547&547%62V??V8<8y   b% I))9I  + % %#"'%#"&547&547%62q2ZZ2IzyV)??V8<8)>~>[   2 b% I))9I %#!"&54>3 72 &6 }XX}.GuLlLuG.>mmUmEEm> /?O_o54&+";2654&+";2654&+";264&#!"3!2654&+";2654&+";264&#!"3!2654&+";2654&+";2654&+";267#!"&5463!2&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&^BB^^B@B^@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&B^^B@B^^/?#!"&5463!2#!"&5463!2#!"&5463!2#!"&5463!2L44LL44LL44LL44LL44LL44LL44LL44L4LL44LL4LL44LL4LL44LL4LL44LL /?O_o#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(8 (88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88/?O_#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88(@(88((88((88(@(88(@(88((88(@(88((8 (88((88(88((88(88((88(88((88(88((88(88((88y"/&4?62 62,PP&PP,jPn#$"' "/&47 &4?62 62 PP&P&&P&P&P&&P&P#+D++"&=#"&=46;546;232  #"'#"$&6$   @    @  rK56$܏ooo|W@    @   rjK&V|oooܳ0#!"&=463!2  #"'#"$&6$   @ rK56$܏ooo|W@  @ rjK&V|oooܳ)5 $&54762>54&'.7>"&5462zz+i *bkQнQkb* j*LhLLhLzzBm +*i JyhQQhyJ i*+ mJ4LL44LL/?O%+"&=46;2%+"&546;2%+"&546;2+"&546;2+"&546;2`r@@r@@n4&"2#"/+"&/&'#"'&'&547>7&/.=46?67&'&547>3267676;27632Ԗ #H  ,/ 1)  ~'H  (C  ,/ 1)  $H ԖԖm 6%2X  % l2 k r6 [21 ..9Q $ k2 k w3 [20/;Cg+"&546;2+"&546;2+"&546;2!3!2>!'&'!+#!"&5#"&=463!7>3!2!2@@@@@@@`0 o`^BB^`5FN(@(NF5 @@@L%%Ju  @LSyuS@%44%f5#!!!"&5465 7#"' '&/&6762546;2&&??>  LL >  X   &&&AJ A J Wh##!"&5463!2!&'&!"&5!(8((88((`x c`(8`((88(@(8(D 9 8( ,#!"&=46;46;2.  6 $$ @(r^aa@@`(_^aa2NC5.+";26#!26'.#!"3!"547>3!";26/.#!2W  .@   @.$S   S$@   9I   I6>  >%=$4&"2$4&"2#!"&5463!2?!2"'&763!463!2!2&4&&4&&4&&48(@(88(ч::(8@6@*&&*4&&4&&4&&4& (88(@(8888)@)'&&@$0"'&76;46;232  >& $$ `  (r^aa` @`2(^aa$0++"&5#"&54762  >& $$ ^ ?  @(r^aa` ? (^aa #!.'!!!%#!"&547>3!2<<<_@`&& 5@5 @  &&>=(""='#"'&5476.  6 $$   ! (r^aaJ %%(_^aa3#!"'&?&#"3267672#"$&6$3276&@*hQQhwI mʬzzk)'@&('QнQh_   z8zoe$G!"$'"&5463!23267676;2#!"&4?&#"+"&=!2762@hk4&&&GaF * &@&ɆF * Ak4&nf&&&4BHrd@&&4rd  Moe&/?O_o+"&=46;25+"&=46;25+"&=46;2#!"&=463!25#!"&=463!25#!"&=463!24&#!"3!26#!"&5463!2 @  @  @  @  @  @  @    @    @    @   ^B@B^^BB^`@  @ @  @ @  @ @  @ @  @ @  @ 3@  MB^^B@B^^!54&"#!"&546;54 32@Ԗ@8(@(88( p (8jj(88(@(88@7+"&5&5462#".#"#"&5476763232>32@@ @ @KjKך=}\I&:k~&26]S &H&  &H5KKut,4, & x:;*4*&K#+"&546;227654$ >3546;2+"&="&/&546$ <X@@Gv"DװD"vG@@X<4L41!Sk @ G< _bb_ 4.54632&4&&M4&UF &""""& F&M&&M&%/B/%G-Ik"'!"&5463!62#"&54>4.54632#"&54767>4&'&'&54632#"&547>7676'&'.'&54632&4&&M4&UF &""""& FU &'8JSSJ8'&  &'.${{$.'& &M&&M&%/B/%7;&'66'&;4[&$ [2[ $&[  #/37#5#5!#5!!!!!!!#5!#5!5##!35!!! #'+/37;?3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3???? ^>>~??????~??~??^??^^? ^??4&"2#"'.5463!2KjKKjv%'45%5&5L45&% jKKjK@5%%%%54L5&6'k54&"2#"'.5463!2#"&'654'.#32KjKKjv%'45%5&5L45&%%'4$.%%5&55&% jKKjK@5%%%%54L5&6'45%%%54'&55&6' yTdt#!"&'&74676&7>7>76&7>7>76&7>7>76&7>7>63!2#!"3!2676'3!26?6&#!"3!26?6&#!"g(sAeM ,*$/ !'& JP$G] x6,& `   h `   "9Hv@WkNC<.  &k& ( "$p" . #u&#  %!' pJvwEF#  @   @  2#"' #"'.546763!''!0#GG$/!''! 8""8  X! 8" "8  <)!!#"&=!4&"27+#!"&=#"&546;463!232(8&4&&4 8(@(8 qO@8((`(@Oq8(&4&&4&@` (88( Oq (8(`(q!)2"&42#!"&546;7>3!2  Ijjjj3e55e3gr`Ijjjj1GG1rP2327&7>7;"&#"4?2>54.'%3"&#"#ժ!9&WB03& K5!)V?@L' >R>e;&L::%P>vO 'h N_":- &+# : ' +a%3 4'.#"32>54.#"7>7><5'./6$3232#"&#"+JBx)EB_I:I*CRzb3:dtB2P$ $5.3bZF|\8!-T>5Fu\,,jn OrB,7676'5.'732>7"#"&#&#"OA zj=N!}:0e%  y + tD3~U#B4 # g  '2 %/!: T bRU,7}%2"/&6;#"&?62+326323!2>?23&'.'.#"&"$#"#&=>764=464.'&#"&'!~:~!PP!~:~!P6 ,,$$% *'  c2N  ($"LA23Yl !x!*%%%% pP,T NE Q7^oH!+( 3  *Ueeu  wga32632$?23&'.5&'&#"&"5$#"#&=>7>4&54&54>.'&#"&'2#".465!#".'&47>32!4&4>Q6 ,,Faw!*' =~Pl*  ($"LA23Yl  )!* <7@@7<  <7@@7<  pP,T MF Q747ƢHoH!+( 3  tJHQ6  wh',686,'$##$',686,'$##$/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?O_o%+"&=46;2+"&=46;2+"&=46;2#!"&=463!2+"&=46;2#!"&=463!2#!"&=463!2#!"&=463!2        @     @   @   @   s  s    s    s  s  /?O#"'&47632#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2     @     @   @  @          s  s  s  /?O#"&54632 #!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2`      @     @   @  @     @   s  s  s  #"'#!"&5463!2632' mw@www '*wwww."&462!5 !"3!2654&#!"&5463!2pppp@  @ ^BB^^B@B^ppp@@  @    @B^^BB^^k%!7'34#"3276' !7632k[[v  6`%`$65&%[[k `5%&&'4&"2"&'&54 Ԗ!?H?!,,ԖԖmF!&&!Fm,%" $$ ^aa`@^aa-4'.'&"26% 547>7>2"KjKXQqYn 243nYqQ$!+!77!+!$5KK,ԑ ]""]ً 9>H7'3&7#!"&5463!2'&#!"3!26=4?6 !762xtt`  ^Qwww@?6 1B^^B@B^ @(` `\\\P`tt8`  ^Ͼww@w 1^BB^^B~ @` \ \P+Z#!"&5463!12+"3!26=47676#"'&=# #"'.54>;547632www M8 pB^^B@B^ 'sw- 9*##;Noj' #ww@w "^BB^^B  *  "g`81T`PSA:'*4/D#!"&5463!2#"'&#!"3!26=4?632"'&4?62 62www@?6 1 B^^B@B^ @ BRnBBn^ww@w 1 ^BB^^B @ BnnBC"&=!32"'&46;!"'&4762!#"&4762+!54624&&4&&44&&4&&44&&44&&4&&44&&6'&'+"&546;267: &&&& s @  Z&&&&Z +6'&''&'+"&546;267667: : &&&&  s @  :  Z&&&&Z  : z6'&''&47667S: : s @  : 4 : | &546h!!0a   $#!"&5463!2#!"&5463!2&&&&&&&&@&&&&&&&&#!"&5463!2&&&&@&&&&&54646&5- : s  :  :4:  +&5464646;2+"&5&5-  &&&& : s  :  : &&&& :  &54646;2+"&5- &&&& s  : &&&&  62#!"&!"&5463!24 @ &&&&-:&&&& "'&476244444Zf "/&47 &4?62S44444#/54&#!4&+"!"3!;265!26 $$ &&&&&&&&@^aa@&&&&&&&&+^aa54&#!"3!26 $$ &&&&@^aa@&&&&+^aa+74/7654/&#"'&#"32?32?6 $$ }ZZZZ^aaZZZZ^aa#4/&"'&"327> $$ [4h4[j^aa"ZiZJ^aa:F%54&+";264.#"32767632;265467>$ $$ oW  5!"40K(0?i+! ":^aaXRd D4!&.uC$=1/J=^aa.:%54&+4&#!";#"3!2654&+";26 $$ ```^aa^aa/_#"&=46;.'+"&=32+546;2>++"&=.'#"&=46;>7546;232m&&m l&&l m&&m l&&ls&%&&%&&%&&%&&&l m&&m l&&l m&&m ,&%&&%&&%&&%&#/;"/"/&4?'&4?627626.  6 $$ I     ͒(r^aaɒ    (_^aa , "'&4?6262.  6 $$ Z4f44fz(r^aaZ&4ff4(_^aa "4'32>&#" $&6$  WoɒV󇥔 zzz8YW˼[?zz:zz@5K #!#"'&547632!2A4@%&&K%54'u%%&54&K&&4A5K$l$L%%%54'&&J&j&K5K #"/&47!"&=463!&4?632%u'43'K&&%@4AA4&&K&45&%@6%u%%K&j&%K55K&$l$K&&u#5K@!#"'+"&5"/&547632K%K&56$K55K$l$K&&#76%%53'K&&%@4AA4&&K&45&%%u'5K"#"'&54?63246;2632K%u'45%u&&J'45%&L44L&%54'K%5%t%%$65&K%%4LL4@&%%K',"&5#"#"'.'547!34624&bqb>#  5&44& 6Uue7D#  "dž&/#!"&546262"/"/&47'&463!2 &@&&4L  r&4  r L&& 4&&&L rI@& r  L4&& s/"/"/&47'&463!2#!"&546262&4  r L&& &@&&4L  r@@& r  L4&& 4&&&L r##!+"&5!"&=463!46;2!28(`8((8`(88(8((8(8 (8`(88(8((8(88(`8#!"&=463!28(@(88((8 (88((88z5'%+"&5&/&67-.?>46;2%6.@g.L44L.g@. .@g. L44L .g@.g.n.4LL43.n.gg.n.34LL4͙.n.g -  $54&+";264'&+";26/a^    ^aa fm  @ J%55!;263'&#"$4&#"32+#!"&5#"&5463!"&46327632#!2$$8~+(888(+}(`8((8`]]k==k]]8,8e8P88P8`(88(@MMN4&#"327>76$32#"'.#"#"&'.54>54&'&54>7>7>32&z&^&./+>+)>J> Wm7' '"''? &4&c&^|h_bml/J@L@#* #M6:D 35sҟw$ '% ' \t3#!"&=463!2'.54>54''  @ 1O``O1CZZ71O``O1BZZ7@  @ N]SHH[3`)TtbN]SHH[3^)Tt!1&' 547 $4&#"2654632 '&476 ==嘅}(zVl''ٌ@uhyyhu9(}VzD##D# =CU%7.5474&#"2654632%#"'&547.'&476!27632#76$7&'7+NWb=嘧}(zVj\i1  z,X Y[6 $!%'FuJiys?_9ɍ?kyhun(}Vz YF  KA؉La  02-F"@Qsp@_!3%54&+";264'&+";26#!"&'&7>2    #%;"";%#`,@L 5 `   `  L`4LH` `   a 5 L@ #37;?Os!!!!%!!!!%!!!!!!!!%!!4&+";26!!%!!!!74&+";26%#!"&546;546;2!546;232 `@ `@ @@ @ @  @  @  @  @ L44LL4^B@B^^B@B^4L  @@@@    @@   @@    M4LL44L`B^^B``B^^B`L7q.+"&=46;2#"&=".'673!54632#"&=!"+"&=46;2>767>3!546327>7&54>$32dFK1A  0) L.٫C58.H(Ye#3C $=463!22>=463!2#!"&5463!2#!"&5463!2H&&/7#"&463!2!2LhLLhLhLLh! &&&&& &4hLLhLLhLLhL%z< 0&4&& )17&4& &&#!"&5463!2!2\@\\@\\@\\\\ W*#!"&547>3!2!"4&5463!2!2W+B"5P+B@"5^=\@\ \H#t3G#3G:_Ht\\ @+32"'&46;#"&4762&&4&&44&&44&&4@"&=!"'&4762!54624&&44&&44&&4&& !!!3!!0@67&#".'&'#"'#"'32>54'6#!"&5463!28ADAE=\W{O[/5dI kDtpČe1?*w@www (M& B{Wta28r=Ku?RZ^GwT -@www$2+37#546375&#"#3!"&5463ww/Dz?swww@wS88 ww#'.>4&#"26546326"&462!5!&  !5!!=!!%#!"&5463!2B^8(Ԗ>@|K55KK55K^B(8ԖԖ€>v5KK55KKHG4&"&#"2654'32#".'#"'#"&54$327.54632@pp)*Pppp)*Pb '"+`N*(a;2̓c`." b PTY9ppP*)pppP*)b ".`(*Nͣ2ͣ`+"' b MRZB4&"24&"264&"26#"/+"&/&'#"'&547>7&/.=46?67&'&547>3267676;27632#"&'"'#"'&547&'&=4767&547>32626?2#"&'"'#"'&547&'&=4767&547>32626?2ԖLhLKjKLhLKjK "8w s%(  ")v  >  "8x s"+  ")v  <  3zLLz3 3>8L3)x3 3zLLz3 3>8L3)x3 ԖԖ4LL45KK54LL45KK #)0C wZ l/ Y N,& #)0C vZl. Y L0"qG^^Gqq$ ]G)FqqG^^Gqq$ ]G)Fq%O#"'#"&'&4>7>7.546$ '&'&'# '32$7>54'VZ|$2 $ |E~E<| $ 2$|ZV:(t}X(  &%(Hw쉉xH(%& (XZT\MKG<m$4&"24&#!4654&#+32;254'>4'654&'>7+"&'&#!"&5463!6767>763232&4&&4N2`@`%)7&,$)' %/0Ӄy#5 +1 &<$]`{t5KK5$e:1&+'3TF0h4&&4&3M:;b^v+D2 5#$IIJ 2E=\$YJ!$MCeM-+(K55KK5y*%Au]c>q4&"24&'>54'654&'654&+"+322654&5!267+#"'.'&'&'!"&5463!27>;2&4&&4+ 5#bW0/% ')$,&7)%`@``2Nh0##T3'"( 0;e$5KK5 tip<& 1&4&&4&#\=E2&%IURI$#5 2D+v^b;:M2gc]vDEA%!bSV2MK55K(,,MeCM$!I@#"&547&547%6@?V8 b% I)94.""'." 67"'.54632>32+C`\hxeH>Hexh\`C+ED4 #LoP$$Po>Q|I.3MCCM3.I|Q/Z$_dC+I@$$@I+ (@%#!"&5463!2#!"3!:"&5!"&5463!462 ww@  B^^B  4&@&&&4 `  ww   ^B@B^ 24& && &%573#7.";2634&#"35#347>32#!"&5463!2FtIG9;HIxI<,tԩw@wwwz4DD43EEueB&#1s@www .4&"26#!+"'!"&5463"&463!2#2&S3 Ll&c4LL44LL4c@& &{LhLLhL'?#!"&5463!2#!"3!26546;2"/"/&47'&463!2www@B^^B@B^@&4t  r &&`ww@w@^BB^^B@R&t r  4&&@"&5!"&5463!462 #!"&54&>3!2654&#!*.54&>3!24&@&&&4 sw  @B^^B  @w4& && &3@w   ^BB^    I&5!%5!>732#!"&=4632654&'&'.=463!5463!2!2JJSq*5&=CKuuKC=&5*q͍S8( ^B@B^ (8`N`Ѣ΀GtO6)"M36J[E@@E[J63M")6OtG(8`B^^B`8 ',26'&'&76'6'&6&'&6'&4#"7&64 654'.'&'.63226767.547&7662>76#!"&5463!2  /[  . =XĚ4,+"  * +, 1JH'5G:: #L5+@=&#w@wwwP.1GE,ԧ4 4+ ; /5cFO:>JJ>:O9W5$@(b 4 @www'?$4&"2$4&"2#!"&5463!3!267!2#!#!"&5!"'&762&4&&4&&4&&48(@(88(c==c(8*&&*6&4&&4&&4&&4& (88(@(88HH88`(@&&('@1c4&'.54654'&#"#"&#"32632327>7#"&#"#"&54654&54>76763232632   N<;+gC8A`1a99gw|98aIe$IVNz<:LQJ  ,-[% 061I()W,$-7,oIX()oζA;=N0 eTZ  (O#".'&'&'&'.54767>3232>32 e^\4?P bMO0# 382W# & 9C9 Lĉ" 82<*9FF(W283 #0OMb P?4\^e FF9*<28 "L 9C9 & #!"3!2654&#!"&5463!2`B^^B@B^^ީwww@w^BB^^B@B^ww@w#!72#"' #"'.546763YY !''!0#GG$/!''!&UUjZ 8""8  X! 8" "8 GW4.'.#"#".'.'.54>54.'.#"32676#!"&5463!2 1.- +$)  c8 )1)  05.D <90)$9w@wwwW  )1) 7c  )$+ -.1 9$)0< D.59@www,T1# '327.'327.=.547&54632676TC_LҬ#+i!+*pDNBN,y[`m`%i]hbEm}a u&,SXK &$f9s? _#"!#!#!54632V<%'ЭHH (ںT\dksz &54654'>54'6'&&"."&'./"?'&546'&6'&6'&6'&6'&74"727&6/a49[aA)O%-j'&]]5r-%O)@a[9' 0BA; + >HCU  #  $  2  AC: oM=a-6OUwW[q ( - q[WwUP6$C +) (  8&/ &eMa  & $      %+"&54&"32#!"&5463!54 &@&Ԗ`(88(@(88(r&&jj8((88(@(8#'+2#!"&5463"!54&#265!375!35!B^^BB^^B   `^B@B^^BB^  ` !="&462+"&'&'.=476;+"&'&$'.=476; pppp$!$qr % }#ߺppp!E$ rqܢ# % ֻ!)?"&462"&4624&#!"3!26!.#!"#!"&547>3!2/B//B//B//B @   2^B@B^\77\aB//B//B//B/@    ~B^^B@2^5BB52.42##%&'.67#"&=463! 25KK5L4_u:B&1/&.- zB^^B4LvyKjK4L[!^k'!A3;):2*547&5462;U gIv0ZZ0L4@Ԗ@4L2RX='8P8'=XR U;Ig0,3lb??bl34LjjL4*\(88(\}I/#"/'&/'&?'&'&?'&76?'&7676767676` (5 )0 ) *) 0) 5(  (5 )0 )))) 0) 5( *) 0) 5(  )5 )0 )**) 0) 5)  )5 )0 )*5h$4&"24&#!4>54&#"+323254'>4'654&'!267+#"'&#!"&5463!2>767>32!2&4&&4N2$YGB (HGEG HQ#5K4Li!<;5KK5 A# ("/?&}vh4&&4&3M95S+C=,@QQ9@@IJ 2E=L5i>9eME;K55K J7R>@#zD<5=q%3#".'&'&'&'.#"!"3!32>$4&"2#!"#"&?&547&'#"&5463!&546323!2` #A<(H(GY$2NL4K5#aWTƾh&4&&4K5;=!ihv}&?/"( #A  5K2*! Q@.'!&=C+S59M34L=E2 JI UR@@&4&&4&5K;ELf9>igR7J K5h4&"24#"."&#"4&#"".#"!54>7#!"&54.'&'.5463246326326&4&&4IJ 2E=L43M95S+C=,@QQ9@@E;K55K J7R>@#zD9eMZ4&&4&<#5K4LN2$YGB (HGEG HV;5KK5 A# ("/?&}vhi!<4<p4.=!32>332653272673264&"2/#"'#"&5#"&54>767>5463!2@@2*! Q@.'!&=C+S59M34L.9E2 JI UR&4&&4&Lf6Aig6Jy#@>R7J K55K;E@TƾH #A<(H(GY$2NL4K#5#a=4&&4&D=ihv}&?/"( #A  5KK5;+54&#!764/&"2?64/!26 $$ & [6[[j6[&^aa@&4[[6[[6&+^aa+4/&"!"3!277$ $$ [6[ &&[6j[ ^aae6[j[6&&4[j[^aa+4''&"2?;2652?$ $$ [6[[6&&4[^aaf6j[[6[ &&[^aa+4/&"4&+"'&"2? $$ [6&&4[j[6[j^aad6[&& [6[[j^aa   $2>767676&67>?&'4&'.'.'."#&6'&6&'3.'.&'&'&&'&6'&>567>#7>7636''&'&&'.'"6&'6'..'/"&'&76.'7>767&.'"76.7"7"#76'&'.'2#22676767765'4.6326&'.'&'"'>7>&&'.54>'>7>67&'&#674&7767>&/45'.67>76'27".#6'>776'>7647>?6#76'6&'676'&67.'&'6.'.#&'.&6'&.5/a^D&"      4   $!   #          .0"Y +  !       $     "  +       Α      ^aa                        P   ' -( # * $  "  !     * !   (         $      2 ~/$4&"2 #"/&547#"32>32&4&&4V%54'j&&'/덹:,{ &4&&4&V%%l$65&b'Cr! " k[G +;%!5!!5!!5!#!"&5463!2#!"&5463!2#!"&5463!2&&&&&&&&&&&&@&&&&&&&&&&&&{#"'&5&763!2{' **)*)'/!5!#!"&5!3!26=#!5!463!5463!2!2^B@B^&@&`^B`8(@(8`B^ B^^B&&B^(88(^G 76#!"'&? #!"&5476 #"'&5463!2 '&763!2#"'c)'&@**@&('c (&*cc*&' *@&('c'(&*cc*&('c'(&@*19AS[#"&532327#!"&54>322>32"&462 &6 +&'654'32>32"&462QgRp|Kx;CByy 6Fe= BPPB =eF6 ԖV>!pRgQBC;xK|Ԗ{QNa*+%xx5eud_C(+5++5+(C_due2ԖԖ>NQ{u%+*jԖԖp!Ci4/&#"#".'32?64/&#"327.546326#"/&547'#"/&4?632632(* 8( !)(A(')* 8( !USxySSXXVzxTTUSxySSXXVzxT@(  (8 *(('( (8 SSUSx{VXXTTSSUSx{VXXT#!"5467&5432632t,Ԟ;F`j)6,>jK?s !%#!"&7#"&463!2+!'5#8EjjE8@&&&&@XYY&4&&4&qDS%q%N\jx2"&4#"'#"'&7>76326?'&'#"'.'&676326326&'&#"32>'&#"3254?''74&&4&l NnbSVZ bRSD zz DSRb)+USbn \.2Q\dJ'.2Q\dJ.Q2.'Jd\Q2.'Jd`!O` ` &4&&4r$#@B10M5TNT{L5T II T5L;l'OT4M01B@#$*3;$*3;;3*$;3*$: $/ @@Qq`@"%3<2#!"&5!"&5467>3!263! !!#!!46!#!(88(@(8(8(`((8D<++<8(`(8(`8(@(88( 8((`(8((<`(8(``(8||?%#"'&54632#"'&#"32654'&#"#"'&54632|udqܟs] = OfjL?R@T?"& > f?rRX=Edudsq = _MjiL?T@R?E& f > =XRr?b!1E)!34&'.##!"&5#3463!24&+";26#!"&5463!2 08((88(@(8  8((88((`(1  `(88((88(@  `(88(@(8(`#!"&5463!2w@www`@www/%#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&&&&&@'7G$"&462"&462#!"&=463!2"&462#!"&=463!2#!"&=463!2ppppppp @   ppp @    @   Рpppppp  ppp    <L\l|#"'732654'>75"##5!!&54>54&#"'>3235#!"&=463!2!5346=#'73#!"&=463!2#!"&=463!2}mQjB919+i1$AjM_3</BB/.#U_:IdDRE @  k*Gj @   @   TP\BX-@8 C)5Xs J@$3T4+,:;39SG2S.7<  vcc)) %Ll}    5e2#!"&=463%&'&5476!2/&'&#"!#"/&'&=4'&?5732767654'&@02uBo  T25XzrDCBBEh:%)0%HPIP{rQ9f#-+>;I@KM-/Q"@@@#-bZ $&P{<8[;:XICC>.'5oe80#.0(  l0&%,"J&9%$<=DTIcs&/6323276727#"327676767654./&'&'737#"'&'&'&54'&54&#!"3!260% <4"VRt8<@< -#=XYhW8+0$"+dTLx-'I&JKkmuw<=V@!X@ v '|N;!/!$8:IObV;C#V  &   ( mL.A:9 !./KLwPM$@@ /?O_o%54&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!26#!"&5463!2@@@@@@@@@^BB^^B@B^NB^^B@B^^#+3 '$"/&4762%/?/?/?/?%k*66bbbb|<<<bbbbbbbb%k66Ƒbbb<<<<^bbbbbb@M$4&"2!#"4&"2&#"&5!"&5#".54634&>?>;5463!2LhLLh LhLLhL! 'ԖԖ@' !&  ?&&LhLLhL hLLhL jjjj &@6/" &&J#"'676732>54.#"7>76'&54632#"&7>54&#"&54$ ok; -j=yhwi[+PM 3ѩk=J%62>VcaaQ^ ]G"'9r~:`}Ch 0=Z٤W=#uY2BrUI1^Fk[|aL2#!67673254.#"67676'&54632#"&7>54&#"#"&5463ww+U ,iXբW<"uW1AqSH1bdww'74'!3#"&46327&#"326%35#5##33#!"&5463!20U6cc\=hlࠥYmmnnnnw@wwww&46#Ȏ;edwnnnnn@www ]#/#"$&6$3 &#"32>7!5!%##5#5353Еttu{zz{SZC` cot*tq||.EXN#?? ,<!5##673#$".4>2"&5!#2!46#!"&5463!2rM* *M~~M**M~~M*jjj&&&&`P%挐|NN||NN|*jjjj@&&&&@ "'&463!2@4@&Z4@4&@ #!"&4762&&4Z4&&4@@ "'&4762&4@4&@&4&@ "&5462@@4&&44@&&@ 3!!%!!26#!"&5463!2`m` ^BB^^B@B^  `@B^^BB^^@ "'&463!2#!"&4762@4@&&&&44@4&Z4&&4@ "'&463!2@4@&4@4&@ #!"&4762&&4Z4&&4@:#!"&5;2>76%6+".'&$'.5463!2^B@B^,9j9Gv33vG9H9+bI\ A+=66=+A [">nSMA_:B^^B1&c*/11/*{'VO3@/$$/@*?Nh^l+!+"&5462!4&#"!/!#>32]_gTRdgdQV?U I*Gg?!2IbbIJaaiwE3300 084#"$'&6?6332>4.#"#!"&54766$32z䜬m IwhQQhbF*@&('kz   _hQнQGB'(&*eoz(q!#"'&547"'#"'&54>7632&4762.547>32#".'632%k'45%&+~(  (h  &  \(  (  &  ~+54'k%5%l%%l$65+~  &  (  (\  &  h(  (~+%'!)19K4&"24&"26.676&$4&"24&"24&"2#!"'&46$ KjKKj KjKKje2.e<^P,bKjKKjKjKKj KjKKj##LlLKjKKjK jKKjK~-M7>7&54$ LhяW.{+9E=cQdFK1A  0) pJ2`[Q?l&٫C58.H(Y':d 6?32$64&$ #"'#"&'&4>7>7.546'&'&'# '32$7>54'Yj`a#",5NK ~EVZ|$2 $ |: $ 2$|ZV:(t}hfR88T h̲X(  &%(Hw(%& (XZT\MKG{x|!#"'.7#"'&7>3!2%632u  j H{(e 9 1bU#!"&546;5!32#!"&546;5!32#!"&546;5463!5#"&5463!2+!2328((88(``(88((88(``(88((88(`L4`(88(@(88(`4L`(8 (88(@(88((88(@(88((88(@(84L8(@(88((8L48OY"&546226562#"'.#"#"'.'."#"'.'.#"#"&5476$32&"5462И&4&NdN!>! 1X:Dx+  +ww+  +xD:X1 -U !*,*&4&hh&&2NN2D &  ..J< $$ 767#"&'"&547&547&547.'&54>2l4  2cKEooED ) ) Dg-;</- ?.P^P.? -/<;-gYY  .2 L4H|O--O|HeO , , Oeq1Ls26%%4.2,44,2.4%%62sL1qcqAAq4#!#"'&547632!2#"&=!"&=463!54632  @  `     ` ?`   @  @  !    54&+4&+"#"276#!"5467&5432632   `  _ v,Ԝ;G_j)``    _ ԟ7 ,>jL>54'&";;265326#!"5467&5432632    v,Ԝ;G_j) `   `7 ,>jL>X`$"&462#!"&54>72654&'547 7"2654'54622654'54&'46.' &6 &4&&4&yy %:hD:FppG9Fj 8P8 LhL 8P8 E; Dh:% >4&&4&}yyD~s[4Dd=PppP=d>hh>@jY*(88(*Y4LL4Y*(88(*YDw" A4*[s~>M4&"27 $=.54632>32#"' 65#"&4632632 65.5462&4&&4G9& <#5KK5!!5KK5#< &ܤ9Gpp&4&&4&@>buោؐ&$KjKnjjKjK$&jjb>Ppp %!5!#"&5463!!35463!2+32@\\8(@(8\@@\\@\(88(\@ 34#"&54"3#!"&5!"&5>547&5462;U gI@L4@Ԗ@4L2RX='8P8'=XR U;Ig04LjjL4*\(88(\@"4&+32!#!"&+#!"&5463!2pP@@Pjj@@\@\&0pj \\&-B+"&5.5462265462265462+"&5#"&5463!2G9L44L9G&4&&4&&4&&4&&4&L44L &=d4LL4 d=&&`&&&&`&&&&4LL4  &#3CS#!"&5463!2!&'&!"&5!463!2#!"&52#!"&=4632#!"&=463(8((88((`x c`(8@@@`((88(@(8(D 9 8(`@@@@@/?O_o-=%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!!5463!2#!"&5463!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ &&&&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @   `&&&& /?O_o%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!#!"&=!!5463!24&+"#54&+";26=3;26%#!"&5463!463!2!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ 8(@(8 @  @  @  @  @ &&&@8((8@&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @  (88(  @  ``   `` -&&& (88(&@<c$4&"2!#4&"254&+54&+"#";;26=326+"&5!"&5#"&46346?>;463!2KjKKjKjKKj&ԖԖ&&@&&KjKKjK jKKjK .&jjjj&4&@@&&#'1?I54&+54&+"#";;26=326!5!#"&5463!!35463!2+32 \\8(@(8\ \\@\(88(\: #32+53##'53535'575#5#5733#5;2+3@E&&`@@` `@@`&&E%@`@ @ @      @ 0 @!3!57#"&5'7!7!K5@   @5K@@@ #3%4&+"!4&+";265!;26#!"&5463!2&&&&&&&&w@www&&@&&&&@&&@www#354&#!4&+"!"3!;265!26#!"&5463!2&&&&&@&&@&w@www@&@&&&&&&@&:@www-M3)$"'&4762 "'&4762 s 2  .   2 w 2  .   2 w 2    2  ww  2    2  ww M3)"/&47 &4?62"/&47 &4?62S .  2 w 2   .  2 w 2  M . 2    2 .  . 2    2 .M3S)$"' "/&4762"' "/&47623 2  ww  2    2  ww  2    2 w 2   .v 2 w 2   .M3s)"'&4?62 62"'&4?62 623 .  . 2    2 .  . 2    2 .   2 w 2v .   2 w 2-Ms3 "'&4762s w 2  .   2 ww  2    2 MS3"/&47 &4?62S .  2 w 2  M . 2    2 .M 3S"' "/&47623 2  ww  2   m 2 w 2   .M-3s"'&4?62 623 .  . 2    2- .   2 w 2/4&#!"3!26#!#!"&54>5!"&5463!2  @ ^B && B^^B@B^ @  MB^%Q= &&& $$ (r^aa(^aa!C#!"&54>;2+";2#!"&54>;2+";2pPPpQh@&&@j8(PppPPpQh@&&@j8(Pp@PppPhQ&&j (8pPPppPhQ&&j (8p!C+"&=46;26=4&+"&5463!2+"&=46;26=4&+"&5463!2Qh@&&@j8(PppPPpQh@&&@j8(PppPPp@hQ&&j (8pPPppP@hQ&&j (8pPPpp@@ #+3;G$#"&5462"&462"&462#"&462"&462"&462"&462#"&54632K54LKj=KjKKjKjKKjL45KKjK<^^^KjKKjppp\]]\jKL45KjKKjKujKKjK4LKjKK^^^jKKjKpppr]]\  $$ ^aaQ^aa,#"&5465654.+"'&47623   #>bqb&44&ɢ5"  #D7euU6 &4&m 1X".4>2".4>24&#""'&#";2>#".'&547&5472632>3=T==T==T==T=v)GG+v@bRRb@=&\Nj!>3lkik3hPTDDTPTDDTPTDDTPTDD|x xXK--K|Mp<# )>dA{RXtfOT# RNftWQ,%4&#!"&=4&#!"3!26#!"&5463!2!28(@(88((88((8\@\\@\\(88(@(88(@(88@\\\\ u'E4#!"3!2676%!54&#!"&=4&#!">#!"&5463!2!2325([5@(\&8((88((8,9.+C\\@\ \6Z]#+#,k(88(@(88(;5E>:5E\\\ \1. $4@"&'&676267>"&462"&462.  > $$ n%%/02 KjKKjKKjKKjKfff^aayy/PccP/jKKjKKjKKjKffff@^aa$4@&'."'.7>2"&462"&462.  > $$ n20/%7KjKKjKKjKKjKfff^aa3/PccP/y jKKjKKjKKjKffff@^aa +7#!"&463!2"&462"&462.  > $$ &&&&KjKKjKKjKKjKfff^aa4&&4&jKKjKKjKKjKffff@^aa#+3C54&+54&+"#";;26=3264&"24&"2$#"'##"3!2@@KjKKjKKjKKjKܒ,gjKKjKKjKKjKXԀ,, #/;GS_kw+"=4;27+"=4;2'+"=4;2#!"=43!2%+"=4;2'+"=4;2+"=4;2'+"=4;2+"=4;2+"=4;2+"=4;2+"=4;2+"=4;54;2!#!"&5463!2`````````````````````p`K55KK55Kp`````````````````````````5KK55KK@*V#"'.#"63232+"&5.5462#"/.#"#"'&547>32327676R?d^7ac77,9xm#@#KjK# ڗXF@Fp:f_ #WIpp&3z h[ 17q%q#::#5KKu't#!X: %#+=&>7p @ *2Fr56565'5&'. #"32325#"'+"&5.5462#"/.#"#"'&547>32327676@ͳ8 2.,#,fk*1x-!#@#KjK# ڗXF@Fp:f_ #WIpp&3z e`vo8t-  :5 [*#::#5KKu't#!X: %#+=&>7p  3$ "/&47 &4?62#!"&=463!2I.  2 w 2   -@). 2    2 . -@@-S$9%"'&4762  /.7> "/&47 &4?62i2  .   2 w E > u > .  2 w 2   2    2  ww !   h. 2    2 . ;#"'&476#"'&7'.'#"'&476' )'s "+5+@ա' )'F*4*Er4M:}}8 GO *4*~ (-/' #"'%#"&7&67%632B;>< V??V --C4 <B=cB5 !% %!b 7I))9I7 #"'.5!".67632y( #  ##@,( )8! !++"&=!"&5#"&=46;546;2!76232-SSS  SS``  K$4&"24&"24&"27"&5467.546267>5.5462 8P88P88P88P8P88P4,CS,4pp4,,4pp4,6d7AL*',4ppP88P8P88P8HP88P8`4Y&+(>EY4PppP4Y4Y4PppP4Y%*54&#"#"/.7!2<'G,')7N;2]=A+#H  0PRH6^;<T%-S#:/*@Z}   >h.%#!"&=46;#"&=463!232#!"&=463!2&&&@@&&&@&&&&&&&&&&&&f&&&&b#!"&=463!2#!"&'&63!2&&&&''%@% &&&&&&&&k%J%#/&'#!53#5!36?!#!'&54>54&#"'6763235 Ź}4NZN4;)3.i%Sin1KXL7觧*  #& *@jC?.>!&1' \%Awc8^;:+54&#"'6763235 Ź}4NZN4;)3.i%PlnEcdJ觧*  #& *-@jC?.>!&1' \%AwcBiC:D'P%! #!"&'&6763!2P &:&? &:&?5"K,)""K,)h#".#""#"&54>54&#"#"'./"'"5327654.54632326732>32YO)I-D%n  "h.=T#)#lQTv%.%P_ % %_P%.%vUPl#)#T=@/#,-91P+R[Ql#)#|'' 59%D-I)OY[R+P19-,##,-91P+R[YO)I-D%95%_P%.%v'3!2#!"&463!5&=462 =462 &546 &&&&&4&r&4&@&4&&4&G݀&&&&f s CK&=462 #"'32=462!2#!"&463!5&'"/&4762%4632e*&4&i76`al&4&&&&&}n  R   R zfOego&&5`3&&&4&&4& D R   R zv"!676"'.5463!2@@w^Cct~5  5~tcC&&@?JV|RIIR|V&&#G!!%4&+";26%4&+";26%#!"&546;546;2!546;232@@@@L44LL4^B@B^^B@B^4L  N4LL44L`B^^B``B^^B`LL4&"2%#"'%.5!#!"&54675#"#"'.7>7&5462!467%632&4&&4  @ o&&}c ;pG=(  8Ai8^^.   &4&&4&` ` fs&& jo/;J!# 2 KAE*,B^^B! ` $ -4&"2#"/&7#"/&767%676$!28P88PQr @ U @ {`PTP88P8P`  @U @rQ!6'&+!!!!2Ѥ 8̙e;<*@8 !GGGQII %764' 64/&"2 $$ f3f4:4^aaf4334f:4:^aa %64'&" 2 $$ :4f3f4F^aa4f44f^aa 764'&"27 2 $$ f:4:f4334^aaf4:4f3^aa %64/&" &"2 $$ -f44f4^aa4f3f4:w^aa@7!!/#35%!'!%j/d jg2|855dc b @! !%!!7!FG)DH:&H dS)U4&"2#"/ $'#"'&5463!2#"&=46;5.546232+>7'&763!2&4&&4f ]wq4qw] `dC&&:FԖF:&&Cd`4&&4& ]] `d[}&&"uFjjFu"&&y}[d#2#!"&546;4 +"&54&" (88(@(88( r&@&Ԗ8((88(@(8@&&jj'3"&462&    .  > $$ Ԗ>aX,fff^aaԖԖa>TX,,~ffff@^aa/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88((88((88((88((88/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88(88((88(88((885E$4&"2%&'&;26%&.$'&;276#!"&5463!2KjKKj   f  \ w@wwwjKKjK"G   ܚ  f   @www   $64'&327/a^ ! ^aaJ@%% 65/ 64'&"2 "/64&"'&476227<ij6j6u%k%~8p8}%%%k%}8p8~%<@% %% !232"'&76;!"/&76  ($>( J &% $%64/&"'&"2#!"&5463!2ff4-4ff4fw@wwwf4f-f4@www/#5#5'&76 764/&"%#!"&5463!248` # \P\w@www4`8  #@  `\P\`@www)4&#!"273276#!"&5463!2& *f4 'w@www`&')4f*@www%5 64'&"3276'7>332#!"&5463!2`'(wƒa8! ,j.( &w@www`4`*'?_`ze<  bw4/*@www-.  6 $$  (r^aaO(_^aa -"'&763!24&#!"3!26#!"&5463!2yB(( @   w@www]#@##   @ @www -#!"'&7624&#!"3!26#!"&5463!2y((@B@u @   w@www###@  @ @www -'&54764&#!"3!26#!"&5463!2@@####@w@wwwB((@@www`%#"'#"&=46;&7#"&=46;632/.#"!2#!!2#!32>?6#  !"'?_  BCbCaf\ + ~2   }0$  q 90r p r%D p u?#!"&=46;#"&=46;54632'.#"!2#!!546;2D a__ g *`-Uh1    ߫}   $^L  4b+"&=.'&?676032654.'.5467546;2'.#"ǟ B{PDg q%%Q{%P46'-N/B).ĝ 9kC< Q 7>W*_x*%K./58`7E%_ ,-3  cVO2")#,)9;J) "!* #VD,'#/&>AX>++"''&=46;267!"&=463!&+"&=463!2+32Ԫ$   pU9ӑ @/*f o  VRfq f=SE!#"&5!"&=463!5!"&=46;&76;2>76;232#!!2#![       % )   "  Jg Uh BW&WX hU g 84&#!!2#!!2#!+"&=#"&=46;5#"&=46;463!2j@jo g|@~vv u n#467!!3'##467!++"'#+"&'#"&=46;'#"&=46;&76;2!6;2!6;232+32QKt# #FNQo!"դѧ !mY Zga~bm] [o"U+, @h h@@X hh @83H\#5"'#"&+73273&#&+5275363534."#22>4.#2>ut 3NtRP*Ho2 Lo@!R(Ozh=,GID2F 8PuE>.'%&TeQ,jm{+>R{?jJrL6V @`7>wmR1q uWei/rr :Vr" $7V4&#"326#"'&76;46;232!5346=#'73#"'&'73267##"&54632BX;4>ID2F +>R{8PuE>.'%&TeQ,jm{?jJrL6 @`rr :Vr3>wmR1q uWei@ \%4&#"326#!"&5463!2+".'&'.5467>767>7>7632!2&%%&&&& &7.' :@$LBWM{#&$h1D!  .I/! Nr&&%%&&&&V?, L=8=9%pEL+%%r@W!<%*',<2(<&L,"r@ \#"&546324&#!"3!26%#!#"'.'.'&'.'.546767>;&%%&&&& &i7qN !/I.  !D1h$&#{MWBL$@: '.&&%%&&&&=XNr%(M&<(2<,'*%<!W@r%%+LEp%9=8=L  +=\d%54#"327354"%###5#5#"'&53327#"'#3632#"'&=4762#3274645"=424'.'&!  7>76#'#3%54'&#"32763##"'&5#327#!"&5463!2BBPJNC'%! B? )#!CC $)  54f"@@ B+,A  A+&+A  ZK35N # J!1331CCC $)w@www2"33FYF~(-%"o4*)$(* (&;;&&9LA3  8334S,;;,WT+<<+T;(\g7x:&&::&&<r%-@www  +=[c}#"'632#542%35!33!3##"'&5#327%54'&#"5#353276%5##"=354'&#"32767654"2 '.'&547>76 3#&'&'3#"'&=47632%#5#"'&53327''RZZ:kid YYY .06 62+YY-06 R[!.'CD''EH$VVX::Y X;:Y fyd/%jG&DC&&CD&O[52. [$C-D..D^^* ly1%=^I86i077S 3 $EWgO%33%OO%35 EEFWt;PP;pt;PP;pqJgTFQ%33&PP%33%R 7>%3!+}{'+"&72'&76;2+"'66;2U &  ( P *'eJ."-dZ-n -'74'&+";27&+";276'56#!"&5463!2~} 7e  ۩w@www"  $Q #'!# @www I-22#!&$/.'.'.'=&7>?>369II ! ' $ !01$$%A' $ ! g  \7@)(7Y   \7@)(7Y @ '5557 ,VWQV.RW=?l%l`~0  !#!#%777 5! R!!XCCfff݀# `,{{{`Og4&"2 &6 $"&462$"&62>7>7>&46.'.'. '.'&7>76 Ԗ HR6L66LGHyU2L  L2UyHHyU2L  L2UyHn X6X  XX ԖԖH6L66L6 L2UyHHyU2L  L2UyHHyU2L n6X  XX  2#!"&54634&"2$4&"2ww@ww||||||w@www||||||| !3 37! $$ n6^55^h ^aaM1^aaP *Cg'.676.7>.'$7>&'.'&'? 7%&'.'.'>767$/u5'&$I7ob?K\[zH,1+.@\7':Yi4&67&'&676'.'>7646&' '7>6'&'&7>7#!"&5463!2PR$++'TJXj7-FC',,&C ."!$28 h /" +p^&+3$ i0(w@www+.i6=Bn \C1XR:#"'jj 8Q.cAj57!? "0D$4" P[ & 2@wwwD"%.5#5>7>;!!76PYhpN!HrD0M C0N#>8\xx: W]oW-X45/%'#.5!5!#"37>#!"&5463!2p>,;$4 5eD+WcEw@wwwK()F ,VhV^9tjA0/@www@#"'&76;46;23   &  ++"&5#"&7632  ^  c  & @#!'&5476!2 &  ^  b '&=!"&=463!546  &    q&8#"'&#"#"5476323276326767q'T1[VA=QQ3qqHih"-bfGw^44O#A?66%CKJA}} !"䒐""A$@C3^q|z=KK?6 lk)  %!%!VVuuu^-m5w}n~7M[264&"264&"2"&546+"&=##"&5'#"&5!467'&766276#"&54632    *<;V<<O@-K<&4'>&4.'.'.'.'.'&6&'.'.6767645.'#.'6&'&7676"&'&627>76'&7>'&'&'&'&766'.7>7676>76&6763>6&'&232.'.6'4."7674.'&#>7626'.'&#"'.'.'&676.67>7>5'&7>.'&'&'&7>7>767&'&67636'.'&67>7>.'.67 \  U7  J#!W! '  " ';%  k )"    '   /7*   I ,6 *&"!   O6* O $.( *.'  .x,  $CN      * 6   7%&&_f& ",VL,G$3@@$+ "  V5 3"  ""#dA++ y0D- %&n 4P'A5j$9E#"c7Y 6" & 8Z(;=I50 ' !!e  R   "+0n?t(-z.'< >R$A"24B@( ~ 9B9, *$        < > ?0D9f?Ae  .(;1.D 4H&.Ct iY% *  7      J  <    W 0%$  ""I! *  D  ,4A'4J" .0f6D4pZ{+*D_wqi;W1G("% %T7F}AG!1#%  JG 3  '.2>Vb%&#'32&'!>?>'&' &>"6&#">&'>26 $$ *b6~#= XP2{&%gx| .W)oOLOsEzG< CK}E $MFD<5+ z^aa$MWM 1>]|YY^D եA<KmE6<" @9I5*^aa>^4./.543232654.#"#".#"32>#"'#"$&547&54632632':XM1h*+D($,/9p`DoC&JV;267676&#!"&=463!267 #!"'&5463!26%8#! &&Z"M>2! ^I 7LRx_@>MN""`=&&*%I},  L7_jj9/%4&#!"3!264&#!"3!26#!"&5463!2  &&&&&&&&19#"'#++"&5#"&5475##"&54763!2"&4628(3- &B..B& -3(8IggI`(8+Ue&.BB.&+8(kk`%-"&5#"&5#"&5#"&5463!2"&4628P8@B\B@B\B@8P8pPPp@`(88(`p.BB.0.BB.(88(Pppͺ!%>&'&#"'.$ $$ ^/(V=$<;$=V).X^aaJ`"(("`J^aa,I4."2>%'%"/'&5%&'&?'&767%476762%6[՛[[՛o ܴ   $ $ " $ $  ՛[[՛[[5` ^ ^ 2` `2 ^ ^ ` 1%#"$54732$%#"$&546$76327668ʴhf킐&^zs,!V[vn) 6<ׂf{z}))Ns3(@ +4&#!"3!2#!"&5463!2#!"&5463!2@&&&f&&&&@&&&&4&&4&@&&&&&&&& `BH+"/##"./#"'.?&5#"&46;'&462!76232!46 `&C6@Bb03eI;:&&&4L4&F Z4&w4) '' 5r&4&&4&&4}G#&/.#./.'&4?63%27>'./&'&7676>767>?>%6})(."2*&@P9A #sGq] #lh<* 46+(  < 5R5"*>%"/ +[>hy  K !/Ui%6&'&676&'&6'.7>%.$76$% $.5476$6?62'.76&&'&676%.76&'..676#"NDQt -okQ//jo_  %&JՂYJA-.-- 9\DtT+X?*<UW3' 26$>>W0 {"F!"E    ^f`$"_]\<`F`FDh>CwlsJ@ ;=?s  :i_^{8+?` ) O`s2RDE58/Kr #"'>7&4$&5mī"#̵$5$"^^W=acE*czk./"&4636$7.'>67.'>65.67>&/>z X^hc^O<q+f$H^XbVS!rȇr?5GD_RV@-FbV=3! G84&3Im<$/6X_D'=NUTL;2KPwtPt=  &ռ ,J~S/#NL,8JsF);??1zIEJpqDIPZXSF6\?5:NR=;.&1 +!"&=!!%!5463!2sQ9Qs***sQNQsBUw wUBFHCCTww%1#"&=!"&=463!54632.  6 $$     ` ?(r^aa    (_^aa%1#!#"'&47632!2.  6 $$   @  ` (r^aa  ?  @  (_^aa/#"'&476324&#!"3!26#!"&5463!2&@& @   w@www& @B@ &  @ @www"&462  >& $$ Ԗ*(r^aaԖԖ (^aa]6#"$54732>%#"'!"&'&7>32'!!!2f:лѪz~u: ((%`V6B^hD%i(]̳ޛ *>6߅r#! 3?^BEa߀#9#36'&632#"'&'&63232#!"&5463!2 Q,&U #+' ;il4L 92<D`w@www`9ܩ6ɽ ]`C477&@wwwD+"&5#"'&=4?5#"'&=4?546;2%6%66546;2  wwwwcB G]B Gty]ty #3C#!+"&5!"&=463!46;2!24&#!"3!26#!"&5463!2@`@`^BB^^B@B^www@w@`@`2@B^^BB^^ww@w'/?P+5#"&547.467&546;532!764'!"+32#323!&ln@ :MM: @nY*Yz--zY*55QDDU9pY-`]]`.X /2I$ t@@/!!/@@3,$,3$p$00&*0&& !P@RV2#"&/#"&/#"&546?#"&546?'&54632%'&54632763276%>S]8T;/M77T7%>ww@ww!"5bBBb// * 8(@(87)(8=%/' #?w@www#~$EE y &L(88e):8(%O r    O?GQaq47&67>&&'&67>&"$32#"#"'654  $&6 $6&$ CoL.*K  Px.* iSƓ i 7J ?~pi{_Я;lLUZ=刈刈_t'<Z :!   @! j`Q7  $ky, Rfk*4LlL=Z=刈&$&546$7%7&'5>]5%w&P?zrSF!| &0 ##!"&5#5!3!3!3!32!546;2!5463) );));;))&&&@@&&&  6 $&727"'%+"'&7&54767%&4762֬>4P t+8?::  ::A W` `EvEEvE<."e$IE&O &EI&{h.`m"&#"&'327>73271[ >+)@ (]:2,C?*%Zx/658:@#N C= E(oE=W'c:#!#"$&6$3 &#"32>7! ڝyy,{ۀہW^F!LC=y:yw߂0H\R%"N^ '&76232762$"&5462"&46274&"&'264&#"'&&#"32$54'>$ $&6$ G>>0yx14J55J5J44J5Fd$?4J55%6E#42F%$fLlLq>>11J44%&4Z%44J54R1F$Z-%45J521Z%F1#:ʎ 9LlL#Qa"'&7622762%"&5462"&546274&#"&'73264&#"'&&#"32654'>#!"&5463!2 55 **.>.-@-R.>.-@-<+*q6- -- 0OpoOxzRrqP6z~{{Prr^aa]054&"#"&5!2654632!#"&57265&'&#".'&'#"&5467%&4>7>3263232654.547'654'63277.'.*#">7?67>?>32#"'7'>3'>3235?KcgA+![,7*  2(-#=  /~[(D?G  |,)"# +)O8,+'6 y{=@0mI#938OAE` -  )y_/FwaH8j7=7?%a % %!?)L J 9=5]~pj  %(1$",I  $@((  +!.S -L__$'-9L 5V+ 6 T+6.8- $ 0 + t |S 16]&#"'&#"67>76'&'&#"67>32764.#"#.32>67>7 $&54>7>7>7rJ@ "kb2)W+ ,5/1   #   Z -!$IOXp7sLCF9vz NAG#/ 5|Հ';RKR/J#=$,9,+$UCS7'2"1  ! / ,   /--ST(::(ep4AM@=I>".)xΤlsY|qK@ %(YQ&N EHv~<Zx'#"&5467&6?2?'&"/.7.546326#"&'&/7264/7'764&"'?>>32.AUpIUxYE.A %%%h% %hJ%D,FZxULs TgxUJrVD %hJ%@/LefL.C %Jh%CV sNUxϠ@.FZyUHpVA %h&%% %Ji%CWpIUybJ/Uy^G,D %Jh%@U sMt UC %hJ%C-KfyEX[_gj&/&'.''67>7>7&'&'&'>76763>7>#&'&'767672'%'7'+"&'&546323267>7%#"'4'6767672,32,+DCCQLDf' % :/d B 4@ }  &!0$?Jfdf-.=6(:!TO? !IG_U% . k*.=; 5gN_X "  ##  292Q41   *6nA;| BS N.  %1$ 6 $nk^ '7GWgw2+"&5463#!"&5463!254&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";26#"&=! B^^BB^^B:FjB^8((`( `(8^BB^^B@B^"vEj^B(8(`(8(/?O_o/?2#!"&5463;26=4&+";26=4&+";26=4&+";26=4&+"54&+";2654&+";2654&+";2654&+";2654&+";2654&#!"3!2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";26@&&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`' "&5#"&5&4762!762$"&462B\B@B\BOpP.BB..BB.8$PO広3CQ#".54>32#".546322#"&#"#"54>%".54>32%2#"&54> &X=L|<&X=M{2r_-$$-_rUU%&&5%ő'- "'.546762@FF$@B@$.&,&.]]|q#<<#(BB B%'-%'-'%'-"'%&'"'%.5467%467%62@ll@ll,@GG&!@@@@@@!&+#+#6#+$*`:p:px p=`$>>$&@&@ @&p@ &.A!!"!&2673!"5432!%!254#!5!2654#!%!2#!8Zp?vdΊens6(N[RWu?rt1SrF|iZ@7މoy2IMC~[R yK{T:%,AGK2#!"&5463!!2654'654.#532#532"&5!654&#"327#2#>!!ww@ww~uk'JTMwa| DH> I1q Fj?w@wwwsq*4p9O*¸Z^qh LE "(nz8B M'?"&4624&#"'.'324&#"3267##"&/632632.ʏhhMALR vGhг~~K „yO^   ʏʏВ*LM@!שwwȍde)qrOPqȦs:03=7'.?67'67%'>&%'7%7./6D\$>  "N,?a0#O 1G9'/P(1#00  ($=!F "9|]"RE<6 'o9%8J$\ :\HiTe<?}V#oj? d,6%N#" HlSVY]C =@C4&"2!.#!"4&"2+"&=!"&=#"&546;>3!232^^^Y ^^^`pppp`]ibbi]~^^^e^^^PppPPppP]^^]3;EM2+"&=!"&=#"&546;>;5463!232264&"!.#!"264&" ]`pppp`]ibbi^^^dY !^^^]@PppP@@PppP@]^^] ^^^e^^^ 3$#!#!"&5467!"&47#"&47#"&4762++&2 $$ 2&&&4&&Z4&&##&&4&4&44&m4&m+DP4'&#"32763232674'&!"32763 3264'&$#"32763232> $$ g* o`#ə0#z#l(~̠) -g+^aaF s" +g (* 3#!| #/IK/%*%D= )[^aa !!!'!!77!,/,-a/G t%/;<HTbcq%7.#"32%74'&"32765"/7627#"5'7432#"/7632#"5'7432#"&5'74632 #"/6327#"/6327#"/46329"&/462"&/>21"&/567632#!.547632632  *     X    ^  `    ^  b  c   fu U`59u  4J   l~ ~ F 2    m | O,           ru| u  " )9 $7 $&= $7 $&= $7 $&=  $&=46w`ww`ww`wb`VTEvEEvETVTEvEEvET*VTEvEEvET*EvEEvEEvEEv#^ct#!"&5463!2!&'&!"&5!632#"&'#"/&'&7>766767.76;267674767&5&5&'67.'&'&#3274(8((88((`x c`(8!3;:A0?ݫY   ^U 47D$    74U3I  |L38wtL0`((88(@(8(D 9 8(Q1&(!;  (g- Up~R2(/{E(Xz*Z%(i6CmVo8 #T#!"&5463!2!&'&!"&5!3367653335!3#4.5.'##'&'35(8((88((`x c`(8iFFZcrcZ`((88(@(8(D 9 8(kk" kkJ  ! k#S#!"&5463!2!&'&!"&5!%!5#7>;#!5#35!3#&'&/35!3(8((88((`x c`(8-Kg kL#DCJg  jLD`((88(@(8(D 9 8(jj jjkk kk#8C#!"&5463!2!&'&!"&5!%!5#5327>54&'&#!3#32(8((88((`x c`(8 G]L*COJ?0R\wx48>`((88(@(8(D 9 8(jjRQxk !RY#*2#!"&5463!2!&'&!"&5!!57"&462(8((88((`x c`(8Pppp`((88(@(8(D 9 8(ppp  #*7JR5#5#5#5##!"&5463!2!&'&!"&5##5!"&54765332264&"<(8((88((`x c`(8kޑcO"jKKjK`((88(@(8(D 9 8(SmmS?M&4&&4#9L^#!"&5463!2!&'&!"&5!#"/#"&=46;76276'.'2764'.(8((88((`x c`(8 6ddWW6&44`((88(@(8(D 9 8(. G5{{5]]$5995#3C#!"&5463!2!&'&!"&5!2#!"&5463#"'5632(8((88((`x c`(84LL44LL4l  `((88(@(8(D 9 8(L44LL44L  Z #7K[#!"&5463!2!&'&!"&5!>&'&7!/.?'&6?6.7>'(8((88((`x c`(8` 3  3  3  3 v  ?  `((88(@(8(D 9 8( & & - & &  ?   '6#'. '!67&54632".'654&#"32eaAɢ/PRAids`WXyzOvд:C;A:25@Ң>-05rn`H( ' gQWZc[ -%7' %'-'% %"'&54762[3[MN 3",""3,3"ong$߆]gn$+) ")")" x#W#"&#!+.5467&546326$32327.'#"&5463232654&#"632#".#"oGn\ u_MK'̨|g? CM7MM5,QAAIQqAy{b]BL4PJ9+OABIRo?z.z n6'+s:zcIAC65D*DRRD*wyal@B39E*DRRD*'/7  $&6$ 6277&47' 7'"' 6& 6'lLRRZB|RR>dZZLlLZRR«Z&>«|R ! $&54$7 >54'5PffP牉@s-ff`-c6721>?>././76&/7>?>?>./&31#"$&(@8!IH2hM>'  )-* h'N'!'Og,R"/!YQG54'63&547#5#"=3235#47##6323#324&"26%#!"&5463!2F]kbf$JMM$&N92Z2&`9UW=N9:PO;:dhe\=R +)&')-S99kJ<)UmQ/-Ya^"![Y'(<`X;_L6#)|tWW:;X  #'#3#!"&5463!2) p*xeשw@www0,\8@www9I#"'#"&'&>767&5462#"'.7>32>4."&'&54>32JrO<3>5-&FD(=Gq@C$39aLL²L4 &) @]v q#CO!~󿵂72765'./"#"&'&5 }1R<2" 7MW'$  ;IS7@5sQ@@)R#DvTA ; 0x I)!:> +)C 6.> !-I[4&#"324&#"3264&#"324&#"326&#"#".'7$4$32'#"$&6$32D2)+BB+)3(--(31)+BB+)4'--'4'#!0>R HMŰ9ou7ǖD䣣 R23('3_,--,R23('3_,--,NJ ?uWm%#"'%#"'.5 %&'&7632! ; `u%"(!]#c)(  #"'%#"'.5%&'&76 !  (%##fP_"(!)'+ʼn4I#"$'&6?6332>4.#"#!"&54766$32#!"&=46;46;2z䜬m IwhQQhbF*@&('k@z   _hQнQGB'(&*eozΘ@@`  >. $$ ffff^aafff^aa>"&#"#"&54>7654'&#!"#"&#"#"&54>765'46.'."&54632326323!27654'.5463232632,-,,",:! %]& %@2(/.+*)6! <.$..**"+8#  #Q3,,++#-:#"$$ /:yuxv)%$ /?CG%!5%2#!"&5463!5#5!52#!"&54632#!"&5463#5!5`&&&& &&&&&&&&@&&&&&&&&&&&&%2 &547%#"&632%&546 #"'6\~~\h ~\h\ V V VV%5$4&#"'64'73264&"&#"3272#!"&5463!2}XT==TX}}~>SX}}XS>~}w@www~:xx:~}}Xx9}}9xX}@www/>LXds.327>76 $&6$32762#"/&4762"/&47626+"&46;2'"&=462#"'&4?62E0l,  *"T.D@Yooo@5D [  Z  Z  [ ``[ Z  2 ,l0 (T" .D5@oooY@D, Z  [  [  Z ``EZ  [ 5%!  $&66='&'%77'727'%amlLmf?55>fFtuutFLlLHYC L||L Y˄(E''E*( /?IYiy%+"&=46;2+"&=46;2+"&=46;2+"&=46;2%"&=!#+"&=46;2+"&=46;2+"&=46;2+"&=46;2!54!54>$ +"&=46;2#!"&=@&&@3P > P3&&rrr&&rrr he 4LKM:%%:MKL4WT&&%/9##!"&563!!#!"&5"&5!2!5463!2!5463!2&&&&&&  &&&i@&&@&7'#5&?6262%%o;j|/&jJ%p&j;&i&p/|jţ%Jk%o%  :g"&5462#"&546324&#!"263662>7'&75.''&'&&'&6463!276i~ZYYZ~@OS;+[G[3YUD#o?D&G3I=JyTkBuhNV!WOhuAiSy*'^CC^'*SwwSTvvTSwwSTvvWID\_"[ gq# /3qFr2/ $rg%4 HffHJ4d#!#7!!7!#5!VFNrmNNN N!Y+?Ne%&'&'&7>727>'#&'&'&>2'&'&676'&76$7&'&767>76 '6# <;11x# *# G,T93%/#0vNZ;:8)M:( &C.J}2 %0  ^*  JF &7'X"2LDM" +6 M2+'BQfXV#+] #' L/(eB9  #,8!!!5!!5!5!5!5#26%!!26#!"&5!5&4& &pPPp@@&&@!&@PppP@*  9Q$"&54627"."#"&547>2"'.#"#"&5476$ "'&$ #"&5476$ (}R}hLK NN Ud: xx 8    ,, |2222 MXXM ic,>>,   ̺  '/7?KSck{4&"2$4&"24&"24&"24&"24&"24&"24&"24&"264&"24&#!"3!264&"2#!"&5463!2KjKKjKjKKjKjKKjKKjKKjKjKKjKjKKjKKjKKjKjKKjKLhLLhLKjKKj&&&&KjKKjL44LL44L5jKKjKKjKKjKjKKjKjKKjKjKKjKjKKjKjKKjKjKKjK4LL44LLjKKjK&&&&jKKjK4LL44LL 'E!#"+"&7>76;7676767>'#'"#!"&7>3!2W",&7' #$ &gpf5 O.PqZZdS -V"0kqzTxD!!8p8%'i_F?;kR(` !&)' (2!&6367! &63!2! `B 1LO(+#=)heCQg#s`f4#6q'X|0 -g >IY#6?>7&#!%'.'33#&#"#"/3674'.54636%#"3733#!"&5463!24  : @7vH%hEP{0&<'VFJo1,1.F6A#L44LL44L"% 7x'6 O\JYFw~v^fH$ ! "xdjD"!6`J4LL44LL +3@GXcgqz -<JX{&#"327&76'32>54.#"35#3;5#'#3537+5;3'23764/"+353$4632#"$2#462#"6462""'"&5&5474761256321##%354&'"&#"5#35432354323=#&#"32?4/&54327&#"#"'326'#"=35#5##3327"327'#"'354&3"5#354327&327''"&46327&#"3=#&#"32?"5#354327&3=#&"32?"#3274?67654'&'4/"&#!"&5463!2_gQQh^_~\[[\]_^hQQge<F$$$ !!&&/ !/  !! 00/e&'!"e$   '!!''   8''NgL44LL44LUQghQUk=("  ! =))=2( '! 'L#(>( & DC(>(zL#DzG)<)4LL44LL  BWbjq}+532%+5324&+32763#4&'.546327&#"#"'3265#"&546325&#"32 !264&"2%#'#735#535#535#3'654&+353#!"&5463!29$<=$@?SdO__J-<AA@)7")9,<$.%0*,G3@%)1??.+&((JgfJ*A!&jjjGZYGиwsswPiL>8aA !M77MM77M3! 4erJ]&3YM(, ,%7(#)  ,(@=)M%A20C&Mee(X0&ĖjjjV 8Z8J9N/4$ 8NN88NN  #&:O[ $?b3'7'#3#%54+32%4+324+323'%#5#'#'##337"&##'!!732%#3#3##!"&53733537!572!56373353#'#'#"5#&#!'#'#463!2#"5#"5!&+&+'!!7353273532!2732%#54&+#32#46.+#2#3#3##+53254&".546;#"67+53254&.546;#"#'#'##"54;"&;7335wY-AJF=c(TS)!*RQ+*RQ+Y,B^9^Ft`njUM ') ~PSPRm٘M77Mo7q @)U 8"E(1++NM77Mx378D62W74;9<-A"EA0:A F@1:ؗBf~~""12"4(w$#11#@}}!%+%5(v$:O\zK?* $\amcrVlOO176Nn23266&+"&#"3267;24&+"'&+";27%4&+";2?>23266&+"&#"3267;254+";27#76;2#!"&5463!23%#2%%,,  _3$$2%%M>AL Vb5)LDHeE:< EM j,K'-R M ~M>AR  Vb5)LEHeE:< E J ABI*'! ($rL44LL44Lv%1 %3!x*k $2 %3!;5h n a !(lI;F   rp p8;5h t a !(lI;F ` #k 4LL44LL  2HW[lt#"'5632#6324&'.54327&#"#"&'32767#533275#"=5&#"'#36323#4'&#"'#7532764&"24'&#"327'#"'&'36#!"&5463!2=!9n23BD$ &:BCRM.0AC'0RH`Q03'`.>,&I / * / 8/n-(G@5$ S3=,.B..B02^`o?7je;9G+L44LL44LyE%# Vb;A !p &'F:Aq)%)#orgT$ v2 8)2z948/{ 8AB..B/q?@r<7(g/4LL44LL ?#!"&'24#"&54"&/&6?&5>547&54626=L4@ԕ;U g3 T 2RX='8P8|5 4Ljj U;Ig@   `  "*\(88(]k  &N4#"&54"3 .#"#!"&'7!&7&/&6?&5>547&54626;U gIm*]Z0L4@ԕ=o=CT T 2RX='8P8|5  U;IgXu?bl3@4Ljja`   `  "*\(88(]k/7[%4&+";26%4&+";26%4&+";26!'&'!+#!"&5#"&=463!7>3!2!2@@@@@@0 o`^BB^`5FN(@(NF5@@@u  @LSyuS@%44%,<H#"5432+"=4&#"326=46;2  >. $$ ~Isy9"SgR8vHD w ffff^aam2N+ )H-mF+10*F +fff^aab4&#"32>"#"'&'#"&54632?>;23>5!"3276#"$&6$3 k^?zb=ka`U4J{K_/4^W&  vx :XB0܂ff ) fzzXlz=lapzob35!2BX G@8  ' '=vN$\ff  1 SZz8zX#("/+'547'&4?6276 'D^h  i%5@%[i  h]@]h  i%@5%[i  h^@@)2#"&5476#".5327>OFi-ay~\~;'S{s:D8>)AJfh]F?X{[TC6LlG]v2'"%B];$-o%!2>7>3232>7>322>7>32".'.#"#"&'.#"#"&'.#"#546;!!!!!32#"&54>52#"&54>52#"&54>52-P&+#($P.-P$'#+&PZP&+#"+&P-($P-.P$(#+$P.-P$'#+&P-.P$+#pP@@PpH85K"&ZH85K"&ZH85K"&Z@Pp@@@pMSK5, :&LMSK5, :&LMSK5, :& !!3 ! @@@  #"$$3!!2"jaѻxlalxaaj!!3/"/'62'&63!2'y  `I  yMy `I y'W`#".'.#"32767!"&54>3232654.'&546#&'5&#" 4$%Eӕ;iNL291 ;XxR`f՝Q8TWiWgW:;*:`Qs&?RWXJ8 oNU0 J1F@#) [%6_POQiX(o`_?5"$iʗ\&>bds6aP*< -;iFn* -c1BWg4'.'4.54632#7&'.#"#"'.#"32767'#"&54632326#!"&5463!2#$( 1$6]' !E3P|ad(2S;aF9'EOSej]m] <*rYshpt.#)$78L*khw@wwwB % $/$G6 sP`X):F/fwH1pdlqnmPHuikw_:[9D'@www34."2>$4.#!!2>#!".>3!2QнQQнQQh~wwhfffнQQнQQнQZZQffff#>3!2#!".2>4."fffнQQнQQffffQнQQн ,\!"&?&#"326'3&'!&#"#"'  5467'+#"327#"&463!!'#"&463!2632(#AHs9q ci<= #]$ KjKKjKKjKKjH#j#H&&&KjKKjKg V i jKKjKKjKKjK ..n(([5KK55KK5[poNv<+#"'#"&546;&546$32322$B$22$$*$22$Xڭӯ$22$tX'hs2$ϧkc$22$1c$2F33F3VVT2#$2ԱVT2#$2g#2UU݃ 2$#2UU1݃2 ,u54#"67.632&#"32654'.#"32764.'&$#"7232&'##"&54732654&#"467&5463254632>32#"'&ru&9%" *#͟ O%GR=O&^opC8pP*bY _#$N Pb@6)?+0L15 "4$.Es  5IQ"!@ h "Y7e|J>ziPeneHbIlF>^]@n*9 6[_3#"&54632#.#"32%3#"&54632#.#"326%4&'.'&! ! 7>7>! =39? 6'_ >29? 5'17m-VU--,bW.뮠@Fyu0HC$뮠@Fyu0HC$L= ?? <=! A <`;+"&54&#!+"&5463!2#!"&546;2!26546;2pЇ0pp@Ipp>Sc+"&=46;254&+"&+";2=46;2;2=46;2;2%54&#!";2=;26#!"&5463!2A5DD5A7^6a7MB55B7?5B~```0`rr5A44A5v5AA5f*A``0` !!!! #!"&5463!2ړ7H7jv@vvv':@vvvMUahmrx#"'!"'!#"&547.547.54674&547&54632!62!632!#!627'!%!"67'#77!63!!7357/7'%# %'3/&=&' 5#?&547 6!p4q"""6" 'h*[ |*,@?wAUMpV@˝)Ϳw7({*U%K6=0(M "! O dX$k !! ! b [TDOi @6bxBAݽ5  ɝ:J +3,p x1Fi (R 463!#!"&5%'4&#!"3`а@..@A-XfB$.BB..C} )&54$32&'%&&'67"w`Rd]G{o]>p6sc(@wgmJPAjyYWa͊AZq{HZ:<dv\gx>2ATKn+;"'&#"&#"+6!263 2&#"&#">3267&#">326e~└Ȁ|隚Ν|ū|iyZʬ7Ӕްr|uѥx9[[9jj9ANN+,#ll"BS32fk[/?\%4&+";26%4&+";26%4&+";26%4&+";26%#!"&5467&546326$32]]eeeeee$~i qfN-*#Sjt2"'qCB8!'> !%)-159=AEIMQUY]agkosw{! %! 5!#5#5#5#5#57777????#5!#5!#5!#5!#5!#5!#5!#5#537#5!#5!#5!#5!#5!#55#535353535353%"&546326#"'#32>54.&54>3237.#"Q%%%%%%%%%?iiihOiixiiyiixiiArssrrssr%sssrrssNs%%%%%%%%%%'32#".543232654&#"#"&54654&#"#"&547>326ڞUzrhgrxSПdU 7#"&463!2!2&&4&&&&4&KjKKjKjKKj &&&%&& &&4&&&&4&&&5jKKjKKjKKjK%z 0&4&&3D7&4& %&'S4&"4&"'&"27"&462"&462!2#!"&54>7#"&463!2!2&4&4&4&4KjKKjKjKKj &&&%&& &&4&%&&ے&4"jKKjKKjKKjK%z 0&4&&3D7&4& %& & !'! !%!!!!%"'.763!2o]FooZY@:@!!gf//I62'"/"/"/"/"/"/"/7762762762762762762%"/77627&6?35!5!!3762762'"/"/"/"/"/"/%5#5!4ZSS6SS4SS4SS4SS4SS4SS4ZSS4SS4SS4SS4SS4SS4S-4ZSS4S@4SS4ZSS6SS4SS4SS4SS4SS4S@ZSSSSSSSSSSSSSSZSSSSSSSSSSSSSyZRRR@%:= :+: =RRZSSSSSSSSSSSSSCv!/&'&#""'&#" 32>;232>7>76#!"&54>7'3&547&547>763226323@``` VFaaFV      $. .$     yy .Q5ZE$ ,l*%>>%*>*98(QO!L\p'.'&67'#!##"327&+"&46;2!3'#"&7>;276;2+6267!"'&7&#"(6&#"#"' Dg OOG`n%ELL{@&&Nc,sU&&!Fre&&ss#/,<= #]gL oGkP'r-n&4&2-ir&&?o  4 _5OW! .54>762>7.'.7>+#!"&5#"&5463!2"&462{{BtxG,:`9(0bԿb0(9`:,GxtB&@&&@&K55K`?e==e?1O6# ,  #$  , #6OO&&&&5KK?!"'&'!2673267!'. ."!&54632>321 4q#F""8'go#- #,"tYg>oP$$Po> Zep#)R0+I@$$@I++332++"&=#"&=46;.7>76$  @ ᅪ*r@@r'/2+"&5".4>32!"&=463  &@~[՛[[u˜~gr&`u՛[[՛[~~@r=E32++"&=#"&=46;5&547&'&6;22676;2  >``@``ٱ?E,,=?rH@``@GݧH`jjrBJ463!2+"&=32++"&=#"&=46;5.7676%#"&5   &@~``@``  vXr&@``@+BF`rks463!2+"&=32++"&=#"&=46;5&547'/.?'+"&5463!2+7>6 %#"&5   &@~``@``~4e  0  io@& jV  0  Z9r&@``@Gɞ5o , sp &@k^ , c8~~`r8>KR_32++"&=!+"&=#"&=46;.767666'27&547&#"&'2#" @@ 'Ϋ'sggsww@sgg@@-ssʃl99OOr99FP^l463!2+"&=$'.7>76%#"&=463!2+"&=%#"&54'>%&547.#"254&' &@L?CuГP vY &@;"ޥ5݇ޥ5`&_ڿgwBF@&J_ s&&?%x%xJP\h463!2+"&='32++"&=#"&=46;5.7676632%#"&56'327&7&#"2#" &@L? ߺu``@``} ຒɞueeu9uee&_"|N@``@""|a~lo99r9@9;C2+"&5"/".4>327'&4?627!"&=463  &@Ռ .  N~[՛[[u˜N .  gr&`֌  . Ou՛[[՛[~N  . @r9A'.'&675#"&=46;5"/&4?62"/32+  '֪ \  . 4 .  \r|ݧ憛@\ .    . \@r~9A"/&4?!+"&=##"$7>763546;2!'&4?62  m  - @ݧ憛@& -  @rm4 -  ٮ*   - r+"&5&54>2  @[՛[rdGu՛[[r  ".4>2r[՛[[՛r5՛[[՛[[$2#!37#546375&#"#3!"&5463#22#y/Dz?s!#22#2##2S88 2#V#2L4>32#"&''&5467&5463232>54&#"#"'.Kg&RvgD $ *2% +Z hP=DXZ@7^?1 ۰3O+lh4`M@8'+c+RI2 \ZAhSQ>B>?S2Vhui/,R0+ ZRkmz+>Q2#"'.'&756763232322>4."7 #"'&546n/9bLHG2E"D8_ pdddxO"2xxê_lx2X  !+'5>-pkW[C I I@50Oddd˥Mhfxx^ә #'+/7!5!!5!4&"2!5!4&"24&"2!!! 8P88P 8P88P88P88PP88P8 P88P88P88P8 +N &6 !2#!+"&5!"&=463!46;23!#!"&54>32267632#"_>@`     `  L4Dgy 6Fe=OOU4L>   ` `  4L2y5eud_C(====`L43V &6 #"/#"/&54?'&54?6327632#!"&54>32 7632_>     %%Sy 6Fe=J%>     %65%Sy5eud_C(zz.!6%$!2!!!46;24&"2!54&#!"&&&@ԖV@&&@&&ԖԖ@&3!!! !5!'!53!! #7IeeI7CzCl@@@#2#!"&?.54$3264&"!@մppp((ppp#+/2#!"&?.54$3264&"!264&"!@մ^^^@^^^@((^^^^^^v(#"'%.54632 "'% 632U/@k0G,zD# [k# /tg F Gz  #'#3!) p*xe0,\8T #/DM%2<GQ^lw &'&676676&'&7654&'&&546763"#"'3264&7.>&'%'.767&7667&766747665"'.'&767>3>7&'&'47.'.7676767&76767.'$73>?>67673>#6766666&'&6767.'"'276&67&54&&671&'6757>7&"2654&57>&>&'5#%67>76$7&74>=.''&'&'#'#''&'&'&'65.'&6767.'#%&''&'#2%676765&'&'&7&5&'6.7>&5R4&5S9 W"-J0(/r V"-J0(.)#"6&4pOPppc|o}vQ[60XQW1V  # 5X N"& . ) D>q J:102(z/=f*4!> S5b!%  (!$p8~5..:5I  ~T 4~9p# ! ) & ?()5F 1   d%{v*: @e s|D1d {:*dAA|oYk'&<tuut&v HCXXTR;w 71™ Z*&' 1  9? . $Gv 5k65P.$.`aasa``Z9k'9؋ӗa-*Gl|Me_]`F& OܽsDD!/+``aa``a154&'"&#!!26#!"&5463!2    iLCly5)*Hcelzzlec0hb,,beIVB9@RB9J_L44LL44L44%2"4:I;p!q4bb3p (P`t`P(6EC.7BI64LL44LL  .>$4&'6#".54$ 4.#!"3!2>#!"&5463!2Zjbjj[wٝ]>oӰٯ*-oXL44LL44L')꽽)J)]wL`ֺ۪e4LL44LL;4&#!"3!26#!"&5463!2#54&#!";#"&5463!2  @ ^BB^^B@B^  B^^B@B^`@  MB^^B@B^^>  ^B@B^^5=Um ! !!2#!"&=463!.'!"&=463!>2!2#264&"".54>762".54>762?(``(?b|b?B//B/]]FrdhLhdrF]]FrdhLhdrF@@@(?@@ ?(@9GG9@/B//BaItB!!BtI Ѷ!!ь ItB!!BtI Ѷ!!ь-M32#!"&=46;7&#"&=463!2#>5!!4.'.46ՠ`@`ՠ`MsFFsMMsFFsMojjo@@jj@@<!(!!(!-3?32#!"&=46;7&#"&=463!2+!!64.'#ՠ`@`ՠ`  DqLLqDojjo@@jj@@B>=C-3;32#!"&=46;7&#"&=463!2+!!6.'#ՠ`@`ՠ`UVU96gg6ojjo@@jj@@β**ɍ-G32#!"&=46;7&#"&=463!2#>5!!&'.46ՠ`@`ՠ`MsFFsMkkojjo@@jj@@<!(!33!(!9I2#!"&=4637>7.'!2#!"&=463@b":1P4Y,++,Y4P1:"":1P4Y,++,Y4P1:"b@@@7hVX@K-AA-K@XVh77hVX@K-AA-K@XVh7Aj"#54&#"'54&#"3!26=476=4&#"#54&'&#"#54&'&'2632632#!"&5&=4632>3265K @0.B @0.B#6'&& l @0.B 2' .B A2TA9B;h" d mpPTlLc _4.HK5]0CB.S0CB./#'?&&)$$)0CB. }(AB.z3M2"61d39L/PpuT(Ifc_E`1X"#4&"'&#"3!267654&"#4&"#4&26326#!"&'&5463246326\B B\B&@5K&@"6LB\B B\B sciL}QP%&#"!"3!754?27%>54&#!26=31?>Ijjq,J[j.-tjlV\$B.R1?@B.+?2`$v5K-%5KK5.olRIS+6K5̈$B\B 94E.&ʀ15uE& ԖPjjdXUGJ7!.B P2.B %2@ 7K5(B@KjKj?+fU E,5K~!1.>F.F,Q5*H$b2#!"&=%!"&=463!7!"&'&=4634'&#!">3!!"3!32#!"3!23!26=n$32>32>32#"#.#"#.#"3!27654&#"547654&#"#654&Mye t|]WSSgSY\x{ 70"1i92DU1&=  =&0@c >&/Btd4!*"8K4+"@H@/'= t?_K93-] UlgQQgsW ]#+ i>p&30&VZ&0B/ %3B. "to ){+C4I (  /D0&p0D3[_cg"'&#"3!2676=4&"#54&#"#54&#"#4&'2632632632#!"&'&5463246#!#!#5K)B4J&@#\8P8 @0.B J65K J6k cJ/4qG^\hB2.1!~K5y?^\Vljt-.j[J,qjjI7$?1R.B+.B$`2?gvEo.5KK5%-K6+SIR[&.E49 B\B$5KG#!+"&5!"&=463!2+"&' +"' +"'&5>;2>76;2Y    M .x - N     u  , u ?  LW   #  *:J4'&+326+"'#+"&5463!2  $6& $&6$ UbUI-uu,uuڎLlLAX!Jmf\$ 6uuu,KLlL-[k{276/&'&#"&5463276?6'.#"!276/&'&#"&5463276?6'.#"  $6&  $&6]h - %Lb`J%E 5 ,5R- h - %Lb`J%E 5 ,5R-'uu,uulL/hR    dMLc  NhR   dMLc  N1uuu,LlL@  ' 7 '7 ``H ``H !``H ```H` '%  7' 7'7 ' $&6$ X`(W:,:X`(WLLlLX`(W:BX`(XLlL $ %/9ES[#"&54632$"&4624&"26$4&#"2%#"&462$#"&4632#"32&! 24>  !#"&'.'#"$547.'!6$327&'77'&77N77N'qqqqqPOrqEsttsst}||}uԙ[WQ~,> nP/R U P酛n >,m'77'&77N77N6^Orqqqqqqt棣棣(~|| on[usј^~33pc8{y%cq33dqpf L 54 "2654"'&'"/&477&'.67>326?>< x ,  (-'sI  VCV  Hr'-(  $0@!BHp9[%&!@0$u  ]\\]-$)!IHV D V HI!)$-#36>N"&462."&/.2?2?64/67>&  #!"&5463!2]]]3 $; &|v;$ (CS31 =rM= 4TC(G zw@www]]]($-;,540= sL =45,; @www(2#"$&546327654&#" &#"AZ\@/#%E1/##.1E$![A懇@@\!#21E!6!E13"|! gL&5&'.#4&5!67&'&'5676&'6452>3.'5A5RV[t,G'Q4}-&r! G;>!g12sV&2:#;d=*'5E2/..FD֕71$1>2F!&12,@K r#"&5462>%.#"'&#"#"'>54#".'7654&&5473254&/>7326/632327?&$  $6 $&6$ !&"2&^ u_x^h ;J݃HJǭ qE Dm! M G?̯' %o8 9U(F(ߎLlL&!&!SEm|[n{[<ɪ "p C Di% (K HCέ  pC B m8 @Kނ  HF(LlL "*6%&6$ 7&$5%%6'$2"&4}x3nQH:dΏX e8z' li=! 7So?vM '&7>>7'7>''>76.'6'El:Fg r *t6K3U Z83P)3^I%=9 )<}Jk+C-Wd &U-TE+]Qr-< Q#0 C+M8 3':$ _Q =+If5[ˮ&&SGZoMkܬc#7&#"327#"'&$&546$;#"'654'632ե›fKYYKf¥yͩ䆎L1hvvƚwwkn]*]nlxDLw~?T8bb9SA}+5?F!3267!#"'#"4767%!2$324&#"6327.'!.#"۔c28Ψ-\?@hU0KeFjTlyE3aVsz.b؏W80]TSts<hO_u7bBtSbF/o|V]SHކJ34&#!"3!26#!!2#!"&=463!5!"&5463!2  @ ^B `` B^^B@B^   @ @B^@@^BB^^>3!"&546)2+6'.'.67>76%&F8$.39_0DD40DD0+*M7{L *="# U<-M93#D@U8vk_Y [hD00DD00Dce-JF1 BDN&)@ /1 dy%F#"'&'&'&'&763276?6#"/#"/&54?'&763276"&'&'&5#&763567632#"'&7632654'&#"32>54'&#"'.5463!2#!3>7632#"'&'&#"'&767632yqoq>* 432fba  $B? >B BB AA.-QPPR+ 42 %<ciђ:6& hHGhkG@n`IȌ5 !m(|.mzyPQ-.  je  q>@@?ppgVZE|fb6887a %RB? =B ABBAJvniQP\\PRh!cDS`gΒ 23geFGPHXcCI_ƍ5" n*T.\PQip [*81 / 9@:>t%6#".'.>%6%&7>'.#*.'&676./&'.54>754'&#"%4>327676= >vwd" l "3 /!,+ j2.|%& (N &wh>8X}xc2"W<4<,Z~fdaA`FBIT;hmA<7QC1>[u])  u1V(k1S) - 0 B2* %M ;W(0S[T]I) A 5%R7&&T,Xq&&1X,LΒw%%;#!"&5463!546;2!2!+"&52#!"/&4?63!5! (&&@&&(&&@&&( (  &&@&&@&&&&  #''%#"'&54676%6%% hh @` !   !    #52#"&5476!2#"&5476!2#"'&546        @  @  @    84&"2$4&"2$4&"2#"'&'&7>7.54$ KjKKjKjKKjKjKKjdne4" %!KjKKjKKjKKjKKjKKjK.٫8  !%00C'Z'.W"&462"&462"&462 6?32$6&#"'#"&'5&6&>7>7&54>$ KjKKjKjKKjKjKKjhяW.{+9E=cQdFK1A  0) LlLjKKjKKjKKjKKjKKjKpJ2`[Q?l&٫C58.H(Yee    Y'w(O'R@$#"&#"'>7676327676#" b,XHUmM.U_t,7A3ge z9@xSaQBLb( VU  !!!==w)AU!!77'7'#'#274.#"#32!5'.>537#"76=4>5'.465! KkkK _5 5 #BH1`L I& v6S F!Sr99rS!`` /7K%s}H XV P V  e  Vd/9Q[ $547.546326%>>32"&5%632264&#"64'&""&'&"2>&2654&#";2 P 3>tSU<)tqH+>XX|Wh,:UStW|XX>=X*  ))  +^X^|WX=>X:_.2//a:Ru?  Q%-W|XW>J( =u>XX|WX`  *((*  +2 2X>=XW|E03>$32!>7 '&'&7!6./EUnohiI\0<{ >ORDƚ~˕VƻoR C37J6I`Tb<^M~M8O  5!#!"&!5!!52!5463 ^B@B^`B^^B `B^^"^BB^0;%'#".54>327&$#"32$ !"$&6$3 ##320JUnLnʡ~~&q@tKL}'` - -oxnǑUyl}~~FڎLlLt`(88(   7!' !\W\ d;tZ`_O; }54+";2%54+";2!4&"!4;234;2354;2354>3&546263232632#"&#"26354;2354;2354;2````pp```  !,! -&M<FI(2 ```@PppPpppppp# #   ppppp j#"'&=!;5463!2#!"&=#".'.#!#"&463232>7>;>32#"&'#"!546 %. `@` :,.',-XjjXh-,'.,: kb>PppP>bk .%Z & :k%$> $``6&L')59I"TlԖlT"I95)'L&69GppG9$ >$%k: !+32&#!332 $&6$ ~O88OLlL>pN  iLlL '':Ma4&'#"'.7654.#""'&#"3!267#!"&54676$32#"'.76'&>$#"'.7654'&676mD5)  z{6lP,@KijjOoɎȕ>>[ta) GG 4?a) ll >;_-/ 9GH{zyN@,KԕoN繁y! ?hh>$ D" >â? $ n"&5462'#".54>22654.'&'.54>32#"#*.5./"~~s!m{b6# -SjR,l'(s-6^]Itg))[zxȁZ&+6,4$.X%%Dc* &D~WL}]I0"  YYZvJ@N*CVTR3/A3$#/;'"/fR-,&2-" 7Zr^Na94Rji3.I+ &6W6>N%&60;96@7F6I3+4&#!"3!26%4&#!"3!26 $$ ^aa`@@^aa '7  $ >. %"&546;2#!"&546;2#/a^(^aa(N@@4&#!"3!26 $$ @@^aa`@^aa '  $ >. 7"&5463!2#/a^(n@^aa(N@ %=%#!"'&7!>3!26=!26=!2%"&54&""&546 ##]VTV$KjKKjK$&4&Ԗ&4&>9G!5KK55KK5!&&jj&&#/;Im2+#!"&'#"&463>'.3%4&"26%4&"26%6.326#>;463!232#.+#!"&5#"5KK5sH..Hs5KK5e# )4# %&4&&4&&4&&4&` #4) #%~]eZ&&Ze] E-&&-EKjKj.<<.KjK)#)`"@&&`&&&&`&&)#`)"dXo&&oXG,8&&8!O##!!2#!+"'&7#+"'&7!"'&?63!!"'&?63!6;236;2!2@@8@7 8Q NQ N 8G@ 8GQ NQ N7   8 8  H H  k%  ".>2I20]@]@oo@@oo㔕a22]]p^|11|99|11|(%7'7' ' 7T dltl)qnluul)1$4&"24&"2 &6 +"&5476;2 &6 LhLLhLLhLLhL>  &   &`>hLLhLLhLLhL>&&>G  .7)1!62 1!62he220e22> v +4 [d+ d 135#5&'72!5!#"&'"'#"$547&54$ Eh`X(cYz:L:zYc\$_K`Pa}fiXXiޝfa  (+.>#5#5!5!5!54&+'#"3!267!7!#!"&5463!2U``'    jjV>(>VV>>Vq  ( ^(>VV>>VV=&'&'&'&76'&'&.' #.h8"$Y ''>eX5, ,PtsK25MRLqS;:.K'5R ChhRt(+e^TTu B"$:2~<2HpwTT V/7GWg. %&32?673327>/.'676$4&"2 $&6$   $6& $&6$ d -- m  ,6*6,  mKjKKjoooKzz8zzȎLlLU4>>4-. YG0 )xx) 0GYޞ .jKKjKqoooolzzz80LlLD/7H#"'.7'654&#"'67'.6?>%"&46227#".547|D,=),9#7[͑fx!X: D$ +s)hhijZt<F/*8C,q؜e\r,WBX/C2hhh=tXm>NZ+"&=46;2+"&=4>7>54&#"#"/.7632  >. $$ p=+& 35,W48'3  l zffff^aaP2P: D#;$# $*;? R Cfff^aa'Y >O`"&5462&'.'.76.5632.'#&'.'&6?65\\[( | r [A@[[@A#2#  7* <Y$  +}"(  q87] F  _1 )    #1Ke34&+326+"&=!#!"&763!2#!"&5463!2#>?4.'3#>?4.'3#>?4.'3Xe`64[l7  , L; =+3&98&+)>>+3&98&+)>=+3&88&+)> Wj|r >Q$~d $kaw+-wi[[\;/xgY $kaw+-wi[[\;/xgY $kaw+-wi[[\;/xgYJ\m4.'.'&#"#"'.'&47>7632327>7>54&'&#"327>"&47654'&462"'&476'&462"'&47>&'&462i$ $^"  %%  "^$ $W "@9O?1&&18?t@" W&%%&4KK6pp&46ZaaZ&4mttm ^x -  - x^ = /U7C kkz'[$ =&5%54'4&KK4r7>54 "&54>2"&462%"&54&#""&546 %#"&'&'.7>#"'&'.7>&4&&4&4&&4SZ&4&&44$#&&&j3$"('$&4&[՛[&4&&4F&4&]\&4&$  !D4%  ,\44&&4&4&&4&-Z4&&4&;cX/)#&>B)&4&j9aU0'.4a7&&u՛[[4&&4&@&&]]&&Ώ0 u40 )4#g&'.#"32676%4/&#"326'&#"2632#2+&'%#"'&6?676676632%#"'&6767#"&'&6767#"'.7>327"#"&'&6763"'.7>;7632;>%5K$ "0%>s$ "0%>;;>%5KVL#>H30 \($$(\( єyO2F/{(?0(TK.5sg$ єy#-F/{$70(TK.5sg$L#>H30 \($$(\#(@5"'K58!'"58!'"55"'K#dS$K K$Sdx#@1 w d>N;ET0((? - 2K|1 wd#N;ET0$(? - 2K$#dS$K K$SdxDN\2654& 265462"2654 #"32654>7>54."/&47&'?62 &4&&4&h՛[&4&r$'("$3j&&&#$4[ " @ GB[ "&&Β&&][u&&7a4.'0Ua9j&4&)B>&#)/Xc;u՛ "  " Gi[ Xh#"&54676324&'&#"'>54#"32#"54>54'.#"32>7>767632326#!"&5463!2b )   :4FDN  [1,^JK-*E#9gWRY vm0O w@wwwC22 c@X&!9{MA_"S4b// DR"XljPY < @www%e4.#"32>7676#'.#"#"&54>3232>754&*#"&54>763 >32 ''il$E/  @P@ ^`'W6&!.. ! -P5+ E{n46vLeVz:,SN/ M5M[  ]$[^5iC'2H&!(?]v`* l b$9> =R2 #"&5467%!"&7>3-.7>;%.7>322326/.76/.'&6766/&/&#"&676 &676&6766/&672? =1( H/ '96&@)9<')29% &06##$ J 0 7j)5@"*3%"!M %#K"%Ne 8)'8_(9./=*%8!Q #P"\Q#N&a)<9bR]mp%"'.'&54>76%&54763263 #"/7#"'#"&/%$%322654&#"%'OV9  nt  |\d ϓ[nt  |@D:) ;98'+| j," 41CH^nVz(~R 9\'  r  @L@  @w46HI(+C ,55, f[op@\j;(zV~i/5O#"'&54>32&#" 654'67'"'>54''&'"'6767&546767>7蒓`V BMR B9)̟!SH-77IXmSMH*k#".o;^J qןד>@YM $bKd ү[E";Kx%^6;%T,U:im=Mk).DT4'"&5463267&#" 6;64'.'4'>732676%#!"&5463!2),蛜s5-54&#"#"'654'.#"#"&#"3263232>3232>76 $$ Cf'/'% ( $UL ( #'/'@ 3#@,G)+H+@#3 ^aaX@ _O#NW#O_ .* ##(^aaq[632632#"&#"#".'&#"#".'&54767>7654.54632327&547>P9 B6?K? %O4T% >6>Z64Y=6>%S4N$ ?L?4B @{:y/$ ,'R! F! 8% #)(()#%: !F Q'+%0z:zO_4'.'&54>54&#"#"'654'.#"#"&#"3263232>3232>76#!"&5463!2Cf'.'% ( $VM  ) #'.'@ 3 #A,G)+H+A# 4 w@wwwXA  ?4N$NW&M&L  /* ## + @www O$>?>762'&#"./454327327>7> EpB5 3FAP/h\/NGSL  RP* m95F84f&3Ga4B|wB.\FI*/.?&,5~K % & Y."7n< "-I.M`{ARwJ!FX^dj''''"'7&'7&'7&'7&547'67'67'67'63277774$#"32$   *'ֱ,?g=OO&L&NJBg;1''ֱ.=gCIM $'&&NJBg=.%w؝\\w Ioo<<-NIDg=/%(ײ+AhEHO*"#*OICh=/'(ֲ/=h>ON.]xwڝ]7e[@)6!!"3#"&546%3567654'3!67!4&'7Sgny]K-#75LSl>9V%cPe}&Hn_HȌ=UoLQ1!45647UC" !-9[nx"&46254&"326754&"326754&"26754&"26#".547632632626326'4#"#"54732764&"264.#"327632>#"'"'#"'#"&5#"'67&'327&'&54>3267>7>7>32632632T"8""8)<())(<))))<))<))<))<) Tد{ՐRhx=8 78 n 81 pH_6Soc F@b@?d?uKbM70[f5Y$35KUC<:[;+8 n 87 8/8Zlv]64qE 'YK0-AlB; W#;WS9 &(#-7Z://:/Tr++r,,r++r,,r++r,,r++r,,ʠgxXVעe9222222^KVvF02OO23OO`lF;mhj84DroB@r+@222222C0DP`.r8h9~T4.&o@9 1P%14'!3#"&46327&#"326%35#5##33 $$  }Pcc]321IUΠ?LL?cc4MX &04;0XpD[[DpD,)&&Q 9V\26&".'&'&6?.#"#26327677>'32>&3#'&+"?626&"#!'.'!"&5463!>;26;2!2P P  92#.}SP9::%L \B )spN/9oJ5  !+D`]BgY9+,9% Pk 4P P &NnF!_7*}B<{o0&&B;*<@$ucRRc#@16#37c&@@@ J"@*4^`ED B o/8927 *@OLC!T!323X$BJ@@@&AS 0C 59" 'D/&&D4 88 $5A&%O#!"&547>7>2$7>/.".'&'&2>^B@B^ >FFzn_0P:P2\nzFF> R & p^1P:P1^ & R P2NMJMQ0Rr.B^^B 7:5]yPH!%%"FPy]5:7 = 4 QH!%%!Ht 4 =<"-/ ?1Pp+".'.'.?>;2>7$76&'&%.+"3!26#!"&54767>;2' +~'*OJ%%JN,&x' % ^M,EE,M7 ZE[P*FF*P:5  ^B@B^){$.MK%%KM.$+X)o3 "a 22!] 4  I>"">,&S8JB##B12 ` `B^^B8&ra#11#$R&  "&.2v%/%''%/%7%7'%7'/#&5'&&?&'&?&'&7%27674?6J" 0<=_gNU?DfuYGb7=^H^` =v~yT3GDPO 4Fѭqi_w\ހ!1uS%V_-d 1=U{J8n~r'U4.#".'"3!264&"26+#!"&5463!232+32+32 0P373/./373P0 T=@=T֙֙|`^B@B^^BB^`````*9deG-! !-Ged9IaallkOB^^BB^^B +Yi"&54622#!"&54>;2>+32+32+#!"&5463!2324&#!"3!26֙֙0.I/ OBBO -Q52-)&)-2 ``  ``  `^B@B^^BB^`  @   |kkl"=IYL)CggC0[jM4      B^^BB^^B @  @ !1AQu4.#".'"3!24&"254&#!"3!2654&#!"3!2654&#!"3!26#!54&+"!54&+"!"&5463!2)P90,***,09P)J66S"@8@^B@@B^^BB^Ukc9 9ckU?@@88 @@N@B^````^BB^^!1AQu#!"&4>32>72"&462#!"&=463!25#!"&=463!25#!"&=463!24&#!"3!546;2!546;2!26#!"&5463!2J66J)P90,***,09P)"@8@ @  `@@` ^B@B^^BB^ՀUUkc9 9c`@@88@@2  @ ````@B^^BB^^(%.'"&' $&  #"$&6$ wCιCwjJ~J>LlLśJSSJ͛>6LlL$,  $&6654&$ 3 72&&  lLmzzBl>KlLGzzG>'7#!"&54>7&54>2  62654' '3/U]B,ȍ,B]U/OQнQ>+X}}X0bӃۚӅb0}hQQh>ff#=#!"&4>3272"&462!3!26#!"&5463!;26=!2J66J)Q8PP8Q)  ^B@B^^B``B^VVVld9KK9d` @B^^BB^``^+;K[eu4.#"'"3!264&"254&#!"3!2654&#!"3!26%54&+";2654&#!"3!26!54&#!"!#!"&5463!2"D/@@/D"?,,?pppp@@@@^B@B^^BB^D6]W2@@2W]67MMppp@@@@@@@@n`@B^^BB^^+;K[eu#!"&54>3272"&462#!"&=463!2%#!"&=463!2+"&=46;25#!"&=463!2!3!26#!"&5463!2?,V,?"D/@@/D"pppp@@@  ^B@B^^BB^D7MM76]W2@@2W]֠ppp@@@@@@@@` @B^^BB^^A#"327.#"'63263#".'#"$&546$32326J9"65I).!1iCCu +I\Gw\B!al݇yǙV/]:=B>9+32%#!"&5463!2#"&54>54'&#"#"54654'.#"#"'.54>54'&'&543232654&432#"&54>764&'&'.54632  ?c'p& ?b1w{2V ?#&#9&CY' &.&#+B : &65&*2w1GF1)2<)<'  ( BH=ӊ:NT :O )4:i   F~b` e!}U3i?fRUX|'&'&Ic&Q  *2U.L6* / L:90%>..>%b>+ +z7ymlw45)0 33J@0!! TFL P]=GS -kwm  !*(%6&692? $&6$  '   al@lLlL,& EC h$LlL /37;%"&546734&'4&" 67 54746 #5#5#5ppF::FDFNV^fnv~"/&4?.7&#"!4>3267622"&4"&46262"&42"&4462"$2"&42"&4"&46262"&4"&46262"&42"&4$2"&42"&42"&4  R ,H8JfjQhjG^R,  !4&&4&Z4&&4&4&&4&4&&4&&4&&44&&4&4&&4&Z4&&4&4&&4&4&&4&4&&4&4&&4&&4&&4&Z4&&4&Z4&&4&  R  ,[cGjhQRJ'A, &4&&4Z&4&&4Z&4&&4Z&4&&444&&4&&4&&4Z&4&&4Z&4&&4Z&4&&4&4&&4Z&4&&4Z&4&&4&&4&&4Z&4&&4Z&4&&4%-5=EM}+"&=#!"'+"&=&="&4626"&462&"&462"&462&"&462&"&462#!"&=46;4632676/&?.7&#"!2"&462&"&462&"&462"&462&"&462&"&462"&462&"&462"&462@?AA? @ @R...R@`jlL.h) * * $ %35K.....uvnu....@@jN  * * .t2#K5..R..R. @Hq '&'&54 &7676767654$'.766$76"&462&'&'&7>54.'.7>76ȵ|_ğyv/ۃ⃺k] :Buq CA _kނXVobZZbnW|V 0  Q2- l}O  / :1z q%zG 4( 6Roa ą\< )4 J}%!!#!"&5463!2^B@B^^BB^`@B^^BB^^%#!"&=463!2^B@B^^BB^B^^BB^^ &))!32#!#!"&5463!463!2`B^^B^B@B^^B`^BB^^B@B^B^^BB^`B^^#3%764/764/&"'&"2?2#!"&5463!2    s^B@B^^BB^ג     @B^^BB^^#'7"/"/&4?'&4?62762!!%#!"&5463!2     ^B@B^^BB^    `@B^^BB^^ ! $&6$ .2r`LlLf4LlL#.C&>"'&4762"/&4?62'"'&4762%'.>6.'.>6'>/>76&'&.'&7&'">?4'.677>7.>37654'&'67>776 $&6$  ( 4Z# # & # # & y"6&.JM@& "(XE* $+8 jT?3#'.'&!3!2>?3.'#!57>7'./5!27#'.#!"g%%D-!gg<6WWZe#1=/2*]Y3-,C1 /Dx] VFIq-HD2NK '>*%R= f 07=. f D]\|yu,0>Seu#2#"'&5<>323#3#&'#334'."#"+236'&54.#"5#37326#!"&5463!2 <  zzj k-L+ )[$8=".un/2 ^B@B^^BB^5cy    (ݔI(8?C (3> #"($=@B^^BB^^0K S&'.'&'./674&$#">&>?>'76'# "&#./.'7676767>76$w .~kuBR] T%z+",|ޟj<)(!( ~ˣzF8"{%%#5)}''xJF0"H[$%EJ#% .Gk29(B13"?@ S)5" #9dmW";L65RA0@T.$}i`:f3A%% BM<$q:)BD aa%`]A &c| Ms!  Z 2}i[ F&** < ʣsc"J<&NsF% 0@Wm6&'.6$.7>7 $76".4>2., &>6'"'&7>=GV:e #:$?+% q4g &3hT`ZtQмQQмpAP1LK!:< }҈`dlb,9'  %%($! a3)W)x  оQQоQQcQǡ-җe)Us2XD\ϼYd /?O_o#"=#"=4;543#"=#"=4;543#"=#"=4;543#"=#"=4;543#"=#"=4;543%#!"&5463!2++532325++532325++532325++532325++53232p00pp00pp00pp00pp008((88(@(80pp00pp00pp00pp00pp0     @(88((88     /Q/&'%&/"&=.6?&?&'&6?'.>-#".6?'.>'&6'.>54627>%>76#"'% %6 27 2G f!)p&4&p)!f G2 72  *6 " 47 2G f!)p&4&p)!f G2 72 " 6* !k 3 j&3 %,*&&ր*9% 3&j 3 k!./!>>$,*!k 3.j&3 %Ԝ9*&&ր*ǜ,% 3&j 3 k!*,$>>!/.&6.'&$ &76$76$PutۥiPuGxy Զ[xy -_v١eNuv١e =uʦ[t78X &6# #'7-'%'&$  $6 $&6$ 31NE0gR=|||">"LlL^v!1f2iЂwgfZQQ^>"||||wLlL &ZXblw.'&>'&'&".'.'&&'&'&7>767>67>7626&'&>&'&>'.7>.676'&'&'&'.67.>7>6&'&676&'&676.676&'&>&'&676'.>6/4-LJg-   $  6)j2%+QF)b3FSP 21DK2AW ") ")$? ? 8A& AE5lZm= gG2Sw*&>$5jD GHyX/4F r 1  1""!l=6> 6 ,5./'e    .*|Ed! u & &%& &5d ))66 @ C& 8B @qL?P^7 G-hI[q:"T6 ,6 &/`  L wQ'   A ^   "  $& _  y  * <Copyright Dave Gandy 2016. All rights reserved.Copyright Dave Gandy 2016. All rights reserved.FontAwesomeFontAwesomeRegularRegularFONTLAB:OTFEXPORTFONTLAB:OTFEXPORTFontAwesomeFontAwesomeVersion 4.7.0 2016Version 4.7.0 2016FontAwesomeFontAwesomePlease refer to the Copyright section for the font trademark attribution notices.Please refer to the Copyright section for the font trademark attribution notices.Fort AwesomeFort AwesomeDave GandyDave Gandyhttp://fontawesome.iohttp://fontawesome.iohttp://fontawesome.io/license/http://fontawesome.io/license/      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab cdefghijklmnopqrstuvwxyz{|}~"      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~glassmusicsearchenvelopeheartstar star_emptyuserfilmth_largethth_listokremovezoom_inzoom_outoffsignalcogtrashhomefile_alttimeroad download_altdownloaduploadinbox play_circlerepeatrefreshlist_altlockflag headphones volume_off volume_down volume_upqrcodebarcodetagtagsbookbookmarkprintcamerafontbolditalic text_height text_width align_left align_center align_right align_justifylist indent_left indent_rightfacetime_videopicturepencil map_markeradjusttinteditsharecheckmove step_backward fast_backwardbackwardplaypausestopforward fast_forward step_forwardeject chevron_left chevron_right plus_sign minus_sign remove_signok_sign question_sign info_sign screenshot remove_circle ok_circle ban_circle arrow_left arrow_rightarrow_up arrow_down share_alt resize_full resize_smallexclamation_signgiftleaffireeye_open eye_close warning_signplanecalendarrandomcommentmagnet chevron_up chevron_downretweet shopping_cart folder_close folder_openresize_verticalresize_horizontal bar_chart twitter_sign facebook_sign camera_retrokeycogscomments thumbs_up_altthumbs_down_alt star_half heart_emptysignout linkedin_signpushpin external_linksignintrophy github_sign upload_altlemonphone check_emptybookmark_empty phone_signtwitterfacebookgithubunlock credit_cardrsshddbullhornbell certificate hand_right hand_lefthand_up hand_downcircle_arrow_leftcircle_arrow_rightcircle_arrow_upcircle_arrow_downglobewrenchtasksfilter briefcase fullscreengrouplinkcloudbeakercutcopy paper_clipsave sign_blankreorderulol strikethrough underlinetablemagictruck pinterestpinterest_signgoogle_plus_sign google_plusmoney caret_downcaret_up caret_left caret_rightcolumnssort sort_downsort_up envelope_altlinkedinundolegal dashboard comment_alt comments_altboltsitemapumbrellapaste light_bulbexchangecloud_download cloud_uploaduser_md stethoscopesuitcasebell_altcoffeefood file_text_altbuildinghospital ambulancemedkit fighter_jetbeerh_signf0fedouble_angle_leftdouble_angle_rightdouble_angle_updouble_angle_down angle_left angle_rightangle_up angle_downdesktoplaptoptablet mobile_phone circle_blank quote_left quote_rightspinnercirclereply github_altfolder_close_altfolder_open_alt expand_alt collapse_altsmilefrownmehgamepadkeyboardflag_altflag_checkeredterminalcode reply_allstar_half_emptylocation_arrowcrop code_forkunlink_279 exclamation superscript subscript_283 puzzle_piece microphonemicrophone_offshieldcalendar_emptyfire_extinguisherrocketmaxcdnchevron_sign_leftchevron_sign_rightchevron_sign_upchevron_sign_downhtml5css3anchor unlock_altbullseyeellipsis_horizontalellipsis_vertical_303 play_signticketminus_sign_alt check_minuslevel_up level_down check_sign edit_sign_312 share_signcompasscollapse collapse_top_317eurgbpusdinrjpyrubkrwbtcfile file_textsort_by_alphabet_329sort_by_attributessort_by_attributes_alt sort_by_ordersort_by_order_alt_334_335 youtube_signyoutubexing xing_sign youtube_playdropbox stackexchange instagramflickradnf171bitbucket_signtumblr tumblr_signlong_arrow_down long_arrow_uplong_arrow_leftlong_arrow_rightwindowsandroidlinuxdribbleskype foursquaretrellofemalemalegittipsun_366archivebugvkweiborenren_372stack_exchange_374arrow_circle_alt_left_376dot_circle_alt_378 vimeo_square_380 plus_square_o_382_383_384_385_386_387_388_389uniF1A0f1a1_392_393f1a4_395_396_397_398_399_400f1ab_402_403_404uniF1B1_406_407_408_409_410_411_412_413_414_415_416_417_418_419uniF1C0uniF1C1_422_423_424_425_426_427_428_429_430_431_432_433_434uniF1D0uniF1D1uniF1D2_438_439uniF1D5uniF1D6uniF1D7_443_444_445_446_447_448_449uniF1E0_451_452_453_454_455_456_457_458_459_460_461_462_463_464uniF1F0_466_467f1f3_469_470_471_472_473_474_475_476f1fc_478_479_480_481_482_483_484_485_486_487_488_489_490_491_492_493_494f210_496f212_498_499_500_501_502_503_504_505_506_507_508_509venus_511_512_513_514_515_516_517_518_519_520_521_522_523_524_525_526_527_528_529_530_531_532_533_534_535_536_537_538_539_540_541_542_543_544_545_546_547_548_549_550_551_552_553_554_555_556_557_558_559_560_561_562_563_564_565_566_567_568_569f260f261_572f263_574_575_576_577_578_579_580_581_582_583_584_585_586_587_588_589_590_591_592_593_594_595_596_597_598f27euniF280uniF281_602_603_604uniF285uniF286_607_608_609_610_611_612_613_614_615_616_617_618_619_620_621_622_623_624_625_626_627_628_629uniF2A0uniF2A1uniF2A2uniF2A3uniF2A4uniF2A5uniF2A6uniF2A7uniF2A8uniF2A9uniF2AAuniF2ABuniF2ACuniF2ADuniF2AEuniF2B0uniF2B1uniF2B2uniF2B3uniF2B4uniF2B5uniF2B6uniF2B7uniF2B8uniF2B9uniF2BAuniF2BBuniF2BCuniF2BDuniF2BEuniF2C0uniF2C1uniF2C2uniF2C3uniF2C4uniF2C5uniF2C6uniF2C7uniF2C8uniF2C9uniF2CAuniF2CBuniF2CCuniF2CDuniF2CEuniF2D0uniF2D1uniF2D2uniF2D3uniF2D4uniF2D5uniF2D6uniF2D7uniF2D8uniF2D9uniF2DAuniF2DBuniF2DCuniF2DDuniF2DEuniF2E0uniF2E1uniF2E2uniF2E3uniF2E4uniF2E5uniF2E6uniF2E7_698uniF2E9uniF2EAuniF2EBuniF2ECuniF2EDuniF2EE=O<01hdoit-0.36.0/doc/_static/vendor/font-awesome/fonts/fontawesome-webfont.svg000066400000000000000000015437331423054503100265130ustar00rootroot00000000000000 Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 By ,,, Copyright Dave Gandy 2016. All rights reserved. doit-0.36.0/doc/_static/vendor/font-awesome/fonts/fontawesome-webfont.ttf000066400000000000000000005032541423054503100265020ustar00rootroot00000000000000 PFFTMkGGDEFp OS/22z@X`cmap : gasphglyfMLhead-6hhea $hmtxEy loca\ maxp,8 name㗋ghpostkuːxY_< 3232  '@i33spyrs@  pU]yn2@ zZ@55 zZZ@,_@s@ @(@@@- MM- MM@@@ -`b $ 648""""""@ D@ ,,@  m)@@   ' D9>dY* '    T     @ f %RE    $!k(D'  % %  0%/&p@0 !"""`>N^n~.>N^n~>N^n~ !"""`!@P`p 0@P`p!@P`p\XSB1ݬ        ,,,,,,,,,,,,,tLT$l x T ( dl,4dpH$d,t( !"0# $,$&D'()T**,,-.@./`/00123d4445 556 6\67H78 8`89L9:h:;<>?h?@H@A0ABXBCdCDLDEFG0GHIJ8KLMdN,NNOP`PQ4QR RlS,ST`U0WXZ[@[\<\]^(^_`pb,bddePefg`giLijDk klm@n,oLpqrsxttuD{`||}}~Hl@lH T H`@$\XDTXDP,8d\Hx tXpdxt@ Œ\ ļŸƔ0dʨˀ͔xϰЌ,ш҈ ӌ8,՜`lHش`Tڸ۔@lބ߬lp 4X$l( ` d      ,,8(Xx|T@| !"x##l$$'h(*L,T.L1t1230345t6T7$8 9H::;<<?X@ABCDEHFHGpHHIxJ JKLMN@P@QRSDT ULV`VWXX4XZZ[d[\|]^`aHabcXdetfhghi\jxnp@svwxyz{h|}}\lt4t88LT|| 4xLX(  @lt$xLL HĠT(  ʈˠϔldPՄxpڬTT ވL <H$l4 Pl ,xp,xt d 44,hP 4   4<,,408$8T |!h"$L%0&H'()*0*+,.$.012@234t5$69 ::; ;<(<=4?@ACDFH`HILLLLLLLLLLLLLLLLp7!!!@pp p]!2#!"&463!&54>3!2+@&&&&@+$(($F#+&4&&4&x+#+".4>32".4>32467632DhgZghDDhg-iWDhgZghDDhg-iW&@ (8 2N++NdN+';2N++NdN+'3 8!  #"'#"$&6$ rL46$܏ooo|W%r4L&V|oooܳ%=M%+".'&%&'3!26<.#!";2>767>7#!"&5463!2 %3@m00m@3%    @ :"7..7":6]^B@B^^BB^ $΄+0110+$ (   t1%%1+`B^^B@B^^"'.54632>324 #LoP$$Po>Z$_dC+I@$$@I+"#"'%#"&547&547%62V??V8<8y   b% I))9I  + % %#"'%#"&547&547%62q2ZZ2IzyV)??V8<8)>~>[   2 b% I))9I %#!"&54>3 72 &6 }XX}.GuLlLuG.>mmUmEEm> /?O_o54&+";2654&+";2654&+";264&#!"3!2654&+";2654&+";264&#!"3!2654&+";2654&+";2654&+";267#!"&5463!2&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&^BB^^B@B^@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&B^^B@B^^/?#!"&5463!2#!"&5463!2#!"&5463!2#!"&5463!2L44LL44LL44LL44LL44LL44LL44LL44L4LL44LL4LL44LL4LL44LL4LL44LL /?O_o#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(8 (88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88/?O_#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88(@(88((88((88(@(88(@(88((88(@(88((8 (88((88(88((88(88((88(88((88(88((88(88((88y"/&4?62 62,PP&PP,jPn#$"' "/&47 &4?62 62 PP&P&&P&P&P&&P&P#+D++"&=#"&=46;546;232  #"'#"$&6$   @    @  rK56$܏ooo|W@    @   rjK&V|oooܳ0#!"&=463!2  #"'#"$&6$   @ rK56$܏ooo|W@  @ rjK&V|oooܳ)5 $&54762>54&'.7>"&5462zz+i *bkQнQkb* j*LhLLhLzzBm +*i JyhQQhyJ i*+ mJ4LL44LL/?O%+"&=46;2%+"&546;2%+"&546;2+"&546;2+"&546;2`r@@r@@n4&"2#"/+"&/&'#"'&'&547>7&/.=46?67&'&547>3267676;27632Ԗ #H  ,/ 1)  ~'H  (C  ,/ 1)  $H ԖԖm 6%2X  % l2 k r6 [21 ..9Q $ k2 k w3 [20/;Cg+"&546;2+"&546;2+"&546;2!3!2>!'&'!+#!"&5#"&=463!7>3!2!2@@@@@@@`0 o`^BB^`5FN(@(NF5 @@@L%%Ju  @LSyuS@%44%f5#!!!"&5465 7#"' '&/&6762546;2&&??>  LL >  X   &&&AJ A J Wh##!"&5463!2!&'&!"&5!(8((88((`x c`(8`((88(@(8(D 9 8( ,#!"&=46;46;2.  6 $$ @(r^aa@@`(_^aa2NC5.+";26#!26'.#!"3!"547>3!";26/.#!2W  .@   @.$S   S$@   9I   I6>  >%=$4&"2$4&"2#!"&5463!2?!2"'&763!463!2!2&4&&4&&4&&48(@(88(ч::(8@6@*&&*4&&4&&4&&4& (88(@(8888)@)'&&@$0"'&76;46;232  >& $$ `  (r^aa` @`2(^aa$0++"&5#"&54762  >& $$ ^ ?  @(r^aa` ? (^aa #!.'!!!%#!"&547>3!2<<<_@`&& 5@5 @  &&>=(""='#"'&5476.  6 $$   ! (r^aaJ %%(_^aa3#!"'&?&#"3267672#"$&6$3276&@*hQQhwI mʬzzk)'@&('QнQh_   z8zoe$G!"$'"&5463!23267676;2#!"&4?&#"+"&=!2762@hk4&&&GaF * &@&ɆF * Ak4&nf&&&4BHrd@&&4rd  Moe&/?O_o+"&=46;25+"&=46;25+"&=46;2#!"&=463!25#!"&=463!25#!"&=463!24&#!"3!26#!"&5463!2 @  @  @  @  @  @  @    @    @    @   ^B@B^^BB^`@  @ @  @ @  @ @  @ @  @ @  @ 3@  MB^^B@B^^!54&"#!"&546;54 32@Ԗ@8(@(88( p (8jj(88(@(88@7+"&5&5462#".#"#"&5476763232>32@@ @ @KjKך=}\I&:k~&26]S &H&  &H5KKut,4, & x:;*4*&K#+"&546;227654$ >3546;2+"&="&/&546$ <X@@Gv"DװD"vG@@X<4L41!Sk @ G< _bb_ 4.54632&4&&M4&UF &""""& F&M&&M&%/B/%G-Ik"'!"&5463!62#"&54>4.54632#"&54767>4&'&'&54632#"&547>7676'&'.'&54632&4&&M4&UF &""""& FU &'8JSSJ8'&  &'.${{$.'& &M&&M&%/B/%7;&'66'&;4[&$ [2[ $&[  #/37#5#5!#5!!!!!!!#5!#5!5##!35!!! #'+/37;?3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3???? ^>>~??????~??~??^??^^? ^??4&"2#"'.5463!2KjKKjv%'45%5&5L45&% jKKjK@5%%%%54L5&6'k54&"2#"'.5463!2#"&'654'.#32KjKKjv%'45%5&5L45&%%'4$.%%5&55&% jKKjK@5%%%%54L5&6'45%%%54'&55&6' yTdt#!"&'&74676&7>7>76&7>7>76&7>7>76&7>7>63!2#!"3!2676'3!26?6&#!"3!26?6&#!"g(sAeM ,*$/ !'& JP$G] x6,& `   h `   "9Hv@WkNC<.  &k& ( "$p" . #u&#  %!' pJvwEF#  @   @  2#"' #"'.546763!''!0#GG$/!''! 8""8  X! 8" "8  <)!!#"&=!4&"27+#!"&=#"&546;463!232(8&4&&4 8(@(8 qO@8((`(@Oq8(&4&&4&@` (88( Oq (8(`(q!)2"&42#!"&546;7>3!2  Ijjjj3e55e3gr`Ijjjj1GG1rP2327&7>7;"&#"4?2>54.'%3"&#"#ժ!9&WB03& K5!)V?@L' >R>e;&L::%P>vO 'h N_":- &+# : ' +a%3 4'.#"32>54.#"7>7><5'./6$3232#"&#"+JBx)EB_I:I*CRzb3:dtB2P$ $5.3bZF|\8!-T>5Fu\,,jn OrB,7676'5.'732>7"#"&#&#"OA zj=N!}:0e%  y + tD3~U#B4 # g  '2 %/!: T bRU,7}%2"/&6;#"&?62+326323!2>?23&'.'.#"&"$#"#&=>764=464.'&#"&'!~:~!PP!~:~!P6 ,,$$% *'  c2N  ($"LA23Yl !x!*%%%% pP,T NE Q7^oH!+( 3  *Ueeu  wga32632$?23&'.5&'&#"&"5$#"#&=>7>4&54&54>.'&#"&'2#".465!#".'&47>32!4&4>Q6 ,,Faw!*' =~Pl*  ($"LA23Yl  )!* <7@@7<  <7@@7<  pP,T MF Q747ƢHoH!+( 3  tJHQ6  wh',686,'$##$',686,'$##$/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?O_o%+"&=46;2+"&=46;2+"&=46;2#!"&=463!2+"&=46;2#!"&=463!2#!"&=463!2#!"&=463!2        @     @   @   @   s  s    s    s  s  /?O#"'&47632#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2     @     @   @  @          s  s  s  /?O#"&54632 #!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2`      @     @   @  @     @   s  s  s  #"'#!"&5463!2632' mw@www '*wwww."&462!5 !"3!2654&#!"&5463!2pppp@  @ ^BB^^B@B^ppp@@  @    @B^^BB^^k%!7'34#"3276' !7632k[[v  6`%`$65&%[[k `5%&&'4&"2"&'&54 Ԗ!?H?!,,ԖԖmF!&&!Fm,%" $$ ^aa`@^aa-4'.'&"26% 547>7>2"KjKXQqYn 243nYqQ$!+!77!+!$5KK,ԑ ]""]ً 9>H7'3&7#!"&5463!2'&#!"3!26=4?6 !762xtt`  ^Qwww@?6 1B^^B@B^ @(` `\\\P`tt8`  ^Ͼww@w 1^BB^^B~ @` \ \P+Z#!"&5463!12+"3!26=47676#"'&=# #"'.54>;547632www M8 pB^^B@B^ 'sw- 9*##;Noj' #ww@w "^BB^^B  *  "g`81T`PSA:'*4/D#!"&5463!2#"'&#!"3!26=4?632"'&4?62 62www@?6 1 B^^B@B^ @ BRnBBn^ww@w 1 ^BB^^B @ BnnBC"&=!32"'&46;!"'&4762!#"&4762+!54624&&4&&44&&4&&44&&44&&4&&44&&6'&'+"&546;267: &&&& s @  Z&&&&Z +6'&''&'+"&546;267667: : &&&&  s @  :  Z&&&&Z  : z6'&''&47667S: : s @  : 4 : | &546h!!0a   $#!"&5463!2#!"&5463!2&&&&&&&&@&&&&&&&&#!"&5463!2&&&&@&&&&&54646&5- : s  :  :4:  +&5464646;2+"&5&5-  &&&& : s  :  : &&&& :  &54646;2+"&5- &&&& s  : &&&&  62#!"&!"&5463!24 @ &&&&-:&&&& "'&476244444Zf "/&47 &4?62S44444#/54&#!4&+"!"3!;265!26 $$ &&&&&&&&@^aa@&&&&&&&&+^aa54&#!"3!26 $$ &&&&@^aa@&&&&+^aa+74/7654/&#"'&#"32?32?6 $$ }ZZZZ^aaZZZZ^aa#4/&"'&"327> $$ [4h4[j^aa"ZiZJ^aa:F%54&+";264.#"32767632;265467>$ $$ oW  5!"40K(0?i+! ":^aaXRd D4!&.uC$=1/J=^aa.:%54&+4&#!";#"3!2654&+";26 $$ ```^aa^aa/_#"&=46;.'+"&=32+546;2>++"&=.'#"&=46;>7546;232m&&m l&&l m&&m l&&ls&%&&%&&%&&%&&&l m&&m l&&l m&&m ,&%&&%&&%&&%&#/;"/"/&4?'&4?627626.  6 $$ I     ͒(r^aaɒ    (_^aa , "'&4?6262.  6 $$ Z4f44fz(r^aaZ&4ff4(_^aa "4'32>&#" $&6$  WoɒV󇥔 zzz8YW˼[?zz:zz@5K #!#"'&547632!2A4@%&&K%54'u%%&54&K&&4A5K$l$L%%%54'&&J&j&K5K #"/&47!"&=463!&4?632%u'43'K&&%@4AA4&&K&45&%@6%u%%K&j&%K55K&$l$K&&u#5K@!#"'+"&5"/&547632K%K&56$K55K$l$K&&#76%%53'K&&%@4AA4&&K&45&%%u'5K"#"'&54?63246;2632K%u'45%u&&J'45%&L44L&%54'K%5%t%%$65&K%%4LL4@&%%K',"&5#"#"'.'547!34624&bqb>#  5&44& 6Uue7D#  "dž&/#!"&546262"/"/&47'&463!2 &@&&4L  r&4  r L&& 4&&&L rI@& r  L4&& s/"/"/&47'&463!2#!"&546262&4  r L&& &@&&4L  r@@& r  L4&& 4&&&L r##!+"&5!"&=463!46;2!28(`8((8`(88(8((8(8 (8`(88(8((8(88(`8#!"&=463!28(@(88((8 (88((88z5'%+"&5&/&67-.?>46;2%6.@g.L44L.g@. .@g. L44L .g@.g.n.4LL43.n.gg.n.34LL4͙.n.g -  $54&+";264'&+";26/a^    ^aa fm  @ J%55!;263'&#"$4&#"32+#!"&5#"&5463!"&46327632#!2$$8~+(888(+}(`8((8`]]k==k]]8,8e8P88P8`(88(@MMN4&#"327>76$32#"'.#"#"&'.54>54&'&54>7>7>32&z&^&./+>+)>J> Wm7' '"''? &4&c&^|h_bml/J@L@#* #M6:D 35sҟw$ '% ' \t3#!"&=463!2'.54>54''  @ 1O``O1CZZ71O``O1BZZ7@  @ N]SHH[3`)TtbN]SHH[3^)Tt!1&' 547 $4&#"2654632 '&476 ==嘅}(zVl''ٌ@uhyyhu9(}VzD##D# =CU%7.5474&#"2654632%#"'&547.'&476!27632#76$7&'7+NWb=嘧}(zVj\i1  z,X Y[6 $!%'FuJiys?_9ɍ?kyhun(}Vz YF  KA؉La  02-F"@Qsp@_!3%54&+";264'&+";26#!"&'&7>2    #%;"";%#`,@L 5 `   `  L`4LH` `   a 5 L@ #37;?Os!!!!%!!!!%!!!!!!!!%!!4&+";26!!%!!!!74&+";26%#!"&546;546;2!546;232 `@ `@ @@ @ @  @  @  @  @ L44LL4^B@B^^B@B^4L  @@@@    @@   @@    M4LL44L`B^^B``B^^B`L7q.+"&=46;2#"&=".'673!54632#"&=!"+"&=46;2>767>3!546327>7&54>$32dFK1A  0) L.٫C58.H(Ye#3C $=463!22>=463!2#!"&5463!2#!"&5463!2H&&/7#"&463!2!2LhLLhLhLLh! &&&&& &4hLLhLLhLLhL%z< 0&4&& )17&4& &&#!"&5463!2!2\@\\@\\@\\\\ W*#!"&547>3!2!"4&5463!2!2W+B"5P+B@"5^=\@\ \H#t3G#3G:_Ht\\ @+32"'&46;#"&4762&&4&&44&&44&&4@"&=!"'&4762!54624&&44&&44&&4&& !!!3!!0@67&#".'&'#"'#"'32>54'6#!"&5463!28ADAE=\W{O[/5dI kDtpČe1?*w@www (M& B{Wta28r=Ku?RZ^GwT -@www$2+37#546375&#"#3!"&5463ww/Dz?swww@wS88 ww#'.>4&#"26546326"&462!5!&  !5!!=!!%#!"&5463!2B^8(Ԗ>@|K55KK55K^B(8ԖԖ€>v5KK55KKHG4&"&#"2654'32#".'#"'#"&54$327.54632@pp)*Pppp)*Pb '"+`N*(a;2̓c`." b PTY9ppP*)pppP*)b ".`(*Nͣ2ͣ`+"' b MRZB4&"24&"264&"26#"/+"&/&'#"'&547>7&/.=46?67&'&547>3267676;27632#"&'"'#"'&547&'&=4767&547>32626?2#"&'"'#"'&547&'&=4767&547>32626?2ԖLhLKjKLhLKjK "8w s%(  ")v  >  "8x s"+  ")v  <  3zLLz3 3>8L3)x3 3zLLz3 3>8L3)x3 ԖԖ4LL45KK54LL45KK #)0C wZ l/ Y N,& #)0C vZl. Y L0"qG^^Gqq$ ]G)FqqG^^Gqq$ ]G)Fq%O#"'#"&'&4>7>7.546$ '&'&'# '32$7>54'VZ|$2 $ |E~E<| $ 2$|ZV:(t}X(  &%(Hw쉉xH(%& (XZT\MKG<m$4&"24&#!4654&#+32;254'>4'654&'>7+"&'&#!"&5463!6767>763232&4&&4N2`@`%)7&,$)' %/0Ӄy#5 +1 &<$]`{t5KK5$e:1&+'3TF0h4&&4&3M:;b^v+D2 5#$IIJ 2E=\$YJ!$MCeM-+(K55KK5y*%Au]c>q4&"24&'>54'654&'654&+"+322654&5!267+#"'.'&'&'!"&5463!27>;2&4&&4+ 5#bW0/% ')$,&7)%`@``2Nh0##T3'"( 0;e$5KK5 tip<& 1&4&&4&#\=E2&%IURI$#5 2D+v^b;:M2gc]vDEA%!bSV2MK55K(,,MeCM$!I@#"&547&547%6@?V8 b% I)94.""'." 67"'.54632>32+C`\hxeH>Hexh\`C+ED4 #LoP$$Po>Q|I.3MCCM3.I|Q/Z$_dC+I@$$@I+ (@%#!"&5463!2#!"3!:"&5!"&5463!462 ww@  B^^B  4&@&&&4 `  ww   ^B@B^ 24& && &%573#7.";2634&#"35#347>32#!"&5463!2FtIG9;HIxI<,tԩw@wwwz4DD43EEueB&#1s@www .4&"26#!+"'!"&5463"&463!2#2&S3 Ll&c4LL44LL4c@& &{LhLLhL'?#!"&5463!2#!"3!26546;2"/"/&47'&463!2www@B^^B@B^@&4t  r &&`ww@w@^BB^^B@R&t r  4&&@"&5!"&5463!462 #!"&54&>3!2654&#!*.54&>3!24&@&&&4 sw  @B^^B  @w4& && &3@w   ^BB^    I&5!%5!>732#!"&=4632654&'&'.=463!5463!2!2JJSq*5&=CKuuKC=&5*q͍S8( ^B@B^ (8`N`Ѣ΀GtO6)"M36J[E@@E[J63M")6OtG(8`B^^B`8 ',26'&'&76'6'&6&'&6'&4#"7&64 654'.'&'.63226767.547&7662>76#!"&5463!2  /[  . =XĚ4,+"  * +, 1JH'5G:: #L5+@=&#w@wwwP.1GE,ԧ4 4+ ; /5cFO:>JJ>:O9W5$@(b 4 @www'?$4&"2$4&"2#!"&5463!3!267!2#!#!"&5!"'&762&4&&4&&4&&48(@(88(c==c(8*&&*6&4&&4&&4&&4& (88(@(88HH88`(@&&('@1c4&'.54654'&#"#"&#"32632327>7#"&#"#"&54654&54>76763232632   N<;+gC8A`1a99gw|98aIe$IVNz<:LQJ  ,-[% 061I()W,$-7,oIX()oζA;=N0 eTZ  (O#".'&'&'&'.54767>3232>32 e^\4?P bMO0# 382W# & 9C9 Lĉ" 82<*9FF(W283 #0OMb P?4\^e FF9*<28 "L 9C9 & #!"3!2654&#!"&5463!2`B^^B@B^^ީwww@w^BB^^B@B^ww@w#!72#"' #"'.546763YY !''!0#GG$/!''!&UUjZ 8""8  X! 8" "8 GW4.'.#"#".'.'.54>54.'.#"32676#!"&5463!2 1.- +$)  c8 )1)  05.D <90)$9w@wwwW  )1) 7c  )$+ -.1 9$)0< D.59@www,T1# '327.'327.=.547&54632676TC_LҬ#+i!+*pDNBN,y[`m`%i]hbEm}a u&,SXK &$f9s? _#"!#!#!54632V<%'ЭHH (ںT\dksz &54654'>54'6'&&"."&'./"?'&546'&6'&6'&6'&6'&74"727&6/a49[aA)O%-j'&]]5r-%O)@a[9' 0BA; + >HCU  #  $  2  AC: oM=a-6OUwW[q ( - q[WwUP6$C +) (  8&/ &eMa  & $      %+"&54&"32#!"&5463!54 &@&Ԗ`(88(@(88(r&&jj8((88(@(8#'+2#!"&5463"!54&#265!375!35!B^^BB^^B   `^B@B^^BB^  ` !="&462+"&'&'.=476;+"&'&$'.=476; pppp$!$qr % }#ߺppp!E$ rqܢ# % ֻ!)?"&462"&4624&#!"3!26!.#!"#!"&547>3!2/B//B//B//B @   2^B@B^\77\aB//B//B//B/@    ~B^^B@2^5BB52.42##%&'.67#"&=463! 25KK5L4_u:B&1/&.- zB^^B4LvyKjK4L[!^k'!A3;):2*547&5462;U gIv0ZZ0L4@Ԗ@4L2RX='8P8'=XR U;Ig0,3lb??bl34LjjL4*\(88(\}I/#"/'&/'&?'&'&?'&76?'&7676767676` (5 )0 ) *) 0) 5(  (5 )0 )))) 0) 5( *) 0) 5(  )5 )0 )**) 0) 5)  )5 )0 )*5h$4&"24&#!4>54&#"+323254'>4'654&'!267+#"'&#!"&5463!2>767>32!2&4&&4N2$YGB (HGEG HQ#5K4Li!<;5KK5 A# ("/?&}vh4&&4&3M95S+C=,@QQ9@@IJ 2E=L5i>9eME;K55K J7R>@#zD<5=q%3#".'&'&'&'.#"!"3!32>$4&"2#!"#"&?&547&'#"&5463!&546323!2` #A<(H(GY$2NL4K5#aWTƾh&4&&4K5;=!ihv}&?/"( #A  5K2*! Q@.'!&=C+S59M34L=E2 JI UR@@&4&&4&5K;ELf9>igR7J K5h4&"24#"."&#"4&#"".#"!54>7#!"&54.'&'.5463246326326&4&&4IJ 2E=L43M95S+C=,@QQ9@@E;K55K J7R>@#zD9eMZ4&&4&<#5K4LN2$YGB (HGEG HV;5KK5 A# ("/?&}vhi!<4<p4.=!32>332653272673264&"2/#"'#"&5#"&54>767>5463!2@@2*! Q@.'!&=C+S59M34L.9E2 JI UR&4&&4&Lf6Aig6Jy#@>R7J K55K;E@TƾH #A<(H(GY$2NL4K#5#a=4&&4&D=ihv}&?/"( #A  5KK5;+54&#!764/&"2?64/!26 $$ & [6[[j6[&^aa@&4[[6[[6&+^aa+4/&"!"3!277$ $$ [6[ &&[6j[ ^aae6[j[6&&4[j[^aa+4''&"2?;2652?$ $$ [6[[6&&4[^aaf6j[[6[ &&[^aa+4/&"4&+"'&"2? $$ [6&&4[j[6[j^aad6[&& [6[[j^aa   $2>767676&67>?&'4&'.'.'."#&6'&6&'3.'.&'&'&&'&6'&>567>#7>7636''&'&&'.'"6&'6'..'/"&'&76.'7>767&.'"76.7"7"#76'&'.'2#22676767765'4.6326&'.'&'"'>7>&&'.54>'>7>67&'&#674&7767>&/45'.67>76'27".#6'>776'>7647>?6#76'6&'676'&67.'&'6.'.#&'.&6'&.5/a^D&"      4   $!   #          .0"Y +  !       $     "  +       Α      ^aa                        P   ' -( # * $  "  !     * !   (         $      2 ~/$4&"2 #"/&547#"32>32&4&&4V%54'j&&'/덹:,{ &4&&4&V%%l$65&b'Cr! " k[G +;%!5!!5!!5!#!"&5463!2#!"&5463!2#!"&5463!2&&&&&&&&&&&&@&&&&&&&&&&&&{#"'&5&763!2{' **)*)'/!5!#!"&5!3!26=#!5!463!5463!2!2^B@B^&@&`^B`8(@(8`B^ B^^B&&B^(88(^G 76#!"'&? #!"&5476 #"'&5463!2 '&763!2#"'c)'&@**@&('c (&*cc*&' *@&('c'(&*cc*&('c'(&@*19AS[#"&532327#!"&54>322>32"&462 &6 +&'654'32>32"&462QgRp|Kx;CByy 6Fe= BPPB =eF6 ԖV>!pRgQBC;xK|Ԗ{QNa*+%xx5eud_C(+5++5+(C_due2ԖԖ>NQ{u%+*jԖԖp!Ci4/&#"#".'32?64/&#"327.546326#"/&547'#"/&4?632632(* 8( !)(A(')* 8( !USxySSXXVzxTTUSxySSXXVzxT@(  (8 *(('( (8 SSUSx{VXXTTSSUSx{VXXT#!"5467&5432632t,Ԟ;F`j)6,>jK?s !%#!"&7#"&463!2+!'5#8EjjE8@&&&&@XYY&4&&4&qDS%q%N\jx2"&4#"'#"'&7>76326?'&'#"'.'&676326326&'&#"32>'&#"3254?''74&&4&l NnbSVZ bRSD zz DSRb)+USbn \.2Q\dJ'.2Q\dJ.Q2.'Jd\Q2.'Jd`!O` ` &4&&4r$#@B10M5TNT{L5T II T5L;l'OT4M01B@#$*3;$*3;;3*$;3*$: $/ @@Qq`@"%3<2#!"&5!"&5467>3!263! !!#!!46!#!(88(@(8(8(`((8D<++<8(`(8(`8(@(88( 8((`(8((<`(8(``(8||?%#"'&54632#"'&#"32654'&#"#"'&54632|udqܟs] = OfjL?R@T?"& > f?rRX=Edudsq = _MjiL?T@R?E& f > =XRr?b!1E)!34&'.##!"&5#3463!24&+";26#!"&5463!2 08((88(@(8  8((88((`(1  `(88((88(@  `(88(@(8(`#!"&5463!2w@www`@www/%#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&&&&&@'7G$"&462"&462#!"&=463!2"&462#!"&=463!2#!"&=463!2ppppppp @   ppp @    @   Рpppppp  ppp    <L\l|#"'732654'>75"##5!!&54>54&#"'>3235#!"&=463!2!5346=#'73#!"&=463!2#!"&=463!2}mQjB919+i1$AjM_3</BB/.#U_:IdDRE @  k*Gj @   @   TP\BX-@8 C)5Xs J@$3T4+,:;39SG2S.7<  vcc)) %Ll}    5e2#!"&=463%&'&5476!2/&'&#"!#"/&'&=4'&?5732767654'&@02uBo  T25XzrDCBBEh:%)0%HPIP{rQ9f#-+>;I@KM-/Q"@@@#-bZ $&P{<8[;:XICC>.'5oe80#.0(  l0&%,"J&9%$<=DTIcs&/6323276727#"327676767654./&'&'737#"'&'&'&54'&54&#!"3!260% <4"VRt8<@< -#=XYhW8+0$"+dTLx-'I&JKkmuw<=V@!X@ v '|N;!/!$8:IObV;C#V  &   ( mL.A:9 !./KLwPM$@@ /?O_o%54&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!26#!"&5463!2@@@@@@@@@^BB^^B@B^NB^^B@B^^#+3 '$"/&4762%/?/?/?/?%k*66bbbb|<<<bbbbbbbb%k66Ƒbbb<<<<^bbbbbb@M$4&"2!#"4&"2&#"&5!"&5#".54634&>?>;5463!2LhLLh LhLLhL! 'ԖԖ@' !&  ?&&LhLLhL hLLhL jjjj &@6/" &&J#"'676732>54.#"7>76'&54632#"&7>54&#"&54$ ok; -j=yhwi[+PM 3ѩk=J%62>VcaaQ^ ]G"'9r~:`}Ch 0=Z٤W=#uY2BrUI1^Fk[|aL2#!67673254.#"67676'&54632#"&7>54&#"#"&5463ww+U ,iXբW<"uW1AqSH1bdww'74'!3#"&46327&#"326%35#5##33#!"&5463!20U6cc\=hlࠥYmmnnnnw@wwww&46#Ȏ;edwnnnnn@www ]#/#"$&6$3 &#"32>7!5!%##5#5353Еttu{zz{SZC` cot*tq||.EXN#?? ,<!5##673#$".4>2"&5!#2!46#!"&5463!2rM* *M~~M**M~~M*jjj&&&&`P%挐|NN||NN|*jjjj@&&&&@ "'&463!2@4@&Z4@4&@ #!"&4762&&4Z4&&4@@ "'&4762&4@4&@&4&@ "&5462@@4&&44@&&@ 3!!%!!26#!"&5463!2`m` ^BB^^B@B^  `@B^^BB^^@ "'&463!2#!"&4762@4@&&&&44@4&Z4&&4@ "'&463!2@4@&4@4&@ #!"&4762&&4Z4&&4@:#!"&5;2>76%6+".'&$'.5463!2^B@B^,9j9Gv33vG9H9+bI\ A+=66=+A [">nSMA_:B^^B1&c*/11/*{'VO3@/$$/@*?Nh^l+!+"&5462!4&#"!/!#>32]_gTRdgdQV?U I*Gg?!2IbbIJaaiwE3300 084#"$'&6?6332>4.#"#!"&54766$32z䜬m IwhQQhbF*@&('kz   _hQнQGB'(&*eoz(q!#"'&547"'#"'&54>7632&4762.547>32#".'632%k'45%&+~(  (h  &  \(  (  &  ~+54'k%5%l%%l$65+~  &  (  (\  &  h(  (~+%'!)19K4&"24&"26.676&$4&"24&"24&"2#!"'&46$ KjKKj KjKKje2.e<^P,bKjKKjKjKKj KjKKj##LlLKjKKjK jKKjK~-M7>7&54$ LhяW.{+9E=cQdFK1A  0) pJ2`[Q?l&٫C58.H(Y':d 6?32$64&$ #"'#"&'&4>7>7.546'&'&'# '32$7>54'Yj`a#",5NK ~EVZ|$2 $ |: $ 2$|ZV:(t}hfR88T h̲X(  &%(Hw(%& (XZT\MKG{x|!#"'.7#"'&7>3!2%632u  j H{(e 9 1bU#!"&546;5!32#!"&546;5!32#!"&546;5463!5#"&5463!2+!2328((88(``(88((88(``(88((88(`L4`(88(@(88(`4L`(8 (88(@(88((88(@(88((88(@(84L8(@(88((8L48OY"&546226562#"'.#"#"'.'."#"'.'.#"#"&5476$32&"5462И&4&NdN!>! 1X:Dx+  +ww+  +xD:X1 -U !*,*&4&hh&&2NN2D &  ..J< $$ 767#"&'"&547&547&547.'&54>2l4  2cKEooED ) ) Dg-;</- ?.P^P.? -/<;-gYY  .2 L4H|O--O|HeO , , Oeq1Ls26%%4.2,44,2.4%%62sL1qcqAAq4#!#"'&547632!2#"&=!"&=463!54632  @  `     ` ?`   @  @  !    54&+4&+"#"276#!"5467&5432632   `  _ v,Ԝ;G_j)``    _ ԟ7 ,>jL>54'&";;265326#!"5467&5432632    v,Ԝ;G_j) `   `7 ,>jL>X`$"&462#!"&54>72654&'547 7"2654'54622654'54&'46.' &6 &4&&4&yy %:hD:FppG9Fj 8P8 LhL 8P8 E; Dh:% >4&&4&}yyD~s[4Dd=PppP=d>hh>@jY*(88(*Y4LL4Y*(88(*YDw" A4*[s~>M4&"27 $=.54632>32#"' 65#"&4632632 65.5462&4&&4G9& <#5KK5!!5KK5#< &ܤ9Gpp&4&&4&@>buោؐ&$KjKnjjKjK$&jjb>Ppp %!5!#"&5463!!35463!2+32@\\8(@(8\@@\\@\(88(\@ 34#"&54"3#!"&5!"&5>547&5462;U gI@L4@Ԗ@4L2RX='8P8'=XR U;Ig04LjjL4*\(88(\@"4&+32!#!"&+#!"&5463!2pP@@Pjj@@\@\&0pj \\&-B+"&5.5462265462265462+"&5#"&5463!2G9L44L9G&4&&4&&4&&4&&4&L44L &=d4LL4 d=&&`&&&&`&&&&4LL4  &#3CS#!"&5463!2!&'&!"&5!463!2#!"&52#!"&=4632#!"&=463(8((88((`x c`(8@@@`((88(@(8(D 9 8(`@@@@@/?O_o-=%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!!5463!2#!"&5463!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ &&&&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @   `&&&& /?O_o%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!#!"&=!!5463!24&+"#54&+";26=3;26%#!"&5463!463!2!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ 8(@(8 @  @  @  @  @ &&&@8((8@&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @  (88(  @  ``   `` -&&& (88(&@<c$4&"2!#4&"254&+54&+"#";;26=326+"&5!"&5#"&46346?>;463!2KjKKjKjKKj&ԖԖ&&@&&KjKKjK jKKjK .&jjjj&4&@@&&#'1?I54&+54&+"#";;26=326!5!#"&5463!!35463!2+32 \\8(@(8\ \\@\(88(\: #32+53##'53535'575#5#5733#5;2+3@E&&`@@` `@@`&&E%@`@ @ @      @ 0 @!3!57#"&5'7!7!K5@   @5K@@@ #3%4&+"!4&+";265!;26#!"&5463!2&&&&&&&&w@www&&@&&&&@&&@www#354&#!4&+"!"3!;265!26#!"&5463!2&&&&&@&&@&w@www@&@&&&&&&@&:@www-M3)$"'&4762 "'&4762 s 2  .   2 w 2  .   2 w 2    2  ww  2    2  ww M3)"/&47 &4?62"/&47 &4?62S .  2 w 2   .  2 w 2  M . 2    2 .  . 2    2 .M3S)$"' "/&4762"' "/&47623 2  ww  2    2  ww  2    2 w 2   .v 2 w 2   .M3s)"'&4?62 62"'&4?62 623 .  . 2    2 .  . 2    2 .   2 w 2v .   2 w 2-Ms3 "'&4762s w 2  .   2 ww  2    2 MS3"/&47 &4?62S .  2 w 2  M . 2    2 .M 3S"' "/&47623 2  ww  2   m 2 w 2   .M-3s"'&4?62 623 .  . 2    2- .   2 w 2/4&#!"3!26#!#!"&54>5!"&5463!2  @ ^B && B^^B@B^ @  MB^%Q= &&& $$ (r^aa(^aa!C#!"&54>;2+";2#!"&54>;2+";2pPPpQh@&&@j8(PppPPpQh@&&@j8(Pp@PppPhQ&&j (8pPPppPhQ&&j (8p!C+"&=46;26=4&+"&5463!2+"&=46;26=4&+"&5463!2Qh@&&@j8(PppPPpQh@&&@j8(PppPPp@hQ&&j (8pPPppP@hQ&&j (8pPPpp@@ #+3;G$#"&5462"&462"&462#"&462"&462"&462"&462#"&54632K54LKj=KjKKjKjKKjL45KKjK<^^^KjKKjppp\]]\jKL45KjKKjKujKKjK4LKjKK^^^jKKjKpppr]]\  $$ ^aaQ^aa,#"&5465654.+"'&47623   #>bqb&44&ɢ5"  #D7euU6 &4&m 1X".4>2".4>24&#""'&#";2>#".'&547&5472632>3=T==T==T==T=v)GG+v@bRRb@=&\Nj!>3lkik3hPTDDTPTDDTPTDDTPTDD|x xXK--K|Mp<# )>dA{RXtfOT# RNftWQ,%4&#!"&=4&#!"3!26#!"&5463!2!28(@(88((88((8\@\\@\\(88(@(88(@(88@\\\\ u'E4#!"3!2676%!54&#!"&=4&#!">#!"&5463!2!2325([5@(\&8((88((8,9.+C\\@\ \6Z]#+#,k(88(@(88(;5E>:5E\\\ \1. $4@"&'&676267>"&462"&462.  > $$ n%%/02 KjKKjKKjKKjKfff^aayy/PccP/jKKjKKjKKjKffff@^aa$4@&'."'.7>2"&462"&462.  > $$ n20/%7KjKKjKKjKKjKfff^aa3/PccP/y jKKjKKjKKjKffff@^aa +7#!"&463!2"&462"&462.  > $$ &&&&KjKKjKKjKKjKfff^aa4&&4&jKKjKKjKKjKffff@^aa#+3C54&+54&+"#";;26=3264&"24&"2$#"'##"3!2@@KjKKjKKjKKjKܒ,gjKKjKKjKKjKXԀ,, #/;GS_kw+"=4;27+"=4;2'+"=4;2#!"=43!2%+"=4;2'+"=4;2+"=4;2'+"=4;2+"=4;2+"=4;2+"=4;2+"=4;2+"=4;54;2!#!"&5463!2`````````````````````p`K55KK55Kp`````````````````````````5KK55KK@*V#"'.#"63232+"&5.5462#"/.#"#"'&547>32327676R?d^7ac77,9xm#@#KjK# ڗXF@Fp:f_ #WIpp&3z h[ 17q%q#::#5KKu't#!X: %#+=&>7p @ *2Fr56565'5&'. #"32325#"'+"&5.5462#"/.#"#"'&547>32327676@ͳ8 2.,#,fk*1x-!#@#KjK# ڗXF@Fp:f_ #WIpp&3z e`vo8t-  :5 [*#::#5KKu't#!X: %#+=&>7p  3$ "/&47 &4?62#!"&=463!2I.  2 w 2   -@). 2    2 . -@@-S$9%"'&4762  /.7> "/&47 &4?62i2  .   2 w E > u > .  2 w 2   2    2  ww !   h. 2    2 . ;#"'&476#"'&7'.'#"'&476' )'s "+5+@ա' )'F*4*Er4M:}}8 GO *4*~ (-/' #"'%#"&7&67%632B;>< V??V --C4 <B=cB5 !% %!b 7I))9I7 #"'.5!".67632y( #  ##@,( )8! !++"&=!"&5#"&=46;546;2!76232-SSS  SS``  K$4&"24&"24&"27"&5467.546267>5.5462 8P88P88P88P8P88P4,CS,4pp4,,4pp4,6d7AL*',4ppP88P8P88P8HP88P8`4Y&+(>EY4PppP4Y4Y4PppP4Y%*54&#"#"/.7!2<'G,')7N;2]=A+#H  0PRH6^;<T%-S#:/*@Z}   >h.%#!"&=46;#"&=463!232#!"&=463!2&&&@@&&&@&&&&&&&&&&&&f&&&&b#!"&=463!2#!"&'&63!2&&&&''%@% &&&&&&&&k%J%#/&'#!53#5!36?!#!'&54>54&#"'6763235 Ź}4NZN4;)3.i%Sin1KXL7觧*  #& *@jC?.>!&1' \%Awc8^;:+54&#"'6763235 Ź}4NZN4;)3.i%PlnEcdJ觧*  #& *-@jC?.>!&1' \%AwcBiC:D'P%! #!"&'&6763!2P &:&? &:&?5"K,)""K,)h#".#""#"&54>54&#"#"'./"'"5327654.54632326732>32YO)I-D%n  "h.=T#)#lQTv%.%P_ % %_P%.%vUPl#)#T=@/#,-91P+R[Ql#)#|'' 59%D-I)OY[R+P19-,##,-91P+R[YO)I-D%95%_P%.%v'3!2#!"&463!5&=462 =462 &546 &&&&&4&r&4&@&4&&4&G݀&&&&f s CK&=462 #"'32=462!2#!"&463!5&'"/&4762%4632e*&4&i76`al&4&&&&&}n  R   R zfOego&&5`3&&&4&&4& D R   R zv"!676"'.5463!2@@w^Cct~5  5~tcC&&@?JV|RIIR|V&&#G!!%4&+";26%4&+";26%#!"&546;546;2!546;232@@@@L44LL4^B@B^^B@B^4L  N4LL44L`B^^B``B^^B`LL4&"2%#"'%.5!#!"&54675#"#"'.7>7&5462!467%632&4&&4  @ o&&}c ;pG=(  8Ai8^^.   &4&&4&` ` fs&& jo/;J!# 2 KAE*,B^^B! ` $ -4&"2#"/&7#"/&767%676$!28P88PQr @ U @ {`PTP88P8P`  @U @rQ!6'&+!!!!2Ѥ 8̙e;<*@8 !GGGQII %764' 64/&"2 $$ f3f4:4^aaf4334f:4:^aa %64'&" 2 $$ :4f3f4F^aa4f44f^aa 764'&"27 2 $$ f:4:f4334^aaf4:4f3^aa %64/&" &"2 $$ -f44f4^aa4f3f4:w^aa@7!!/#35%!'!%j/d jg2|855dc b @! !%!!7!FG)DH:&H dS)U4&"2#"/ $'#"'&5463!2#"&=46;5.546232+>7'&763!2&4&&4f ]wq4qw] `dC&&:FԖF:&&Cd`4&&4& ]] `d[}&&"uFjjFu"&&y}[d#2#!"&546;4 +"&54&" (88(@(88( r&@&Ԗ8((88(@(8@&&jj'3"&462&    .  > $$ Ԗ>aX,fff^aaԖԖa>TX,,~ffff@^aa/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88((88((88((88((88/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88(88((88(88((885E$4&"2%&'&;26%&.$'&;276#!"&5463!2KjKKj   f  \ w@wwwjKKjK"G   ܚ  f   @www   $64'&327/a^ ! ^aaJ@%% 65/ 64'&"2 "/64&"'&476227<ij6j6u%k%~8p8}%%%k%}8p8~%<@% %% !232"'&76;!"/&76  ($>( J &% $%64/&"'&"2#!"&5463!2ff4-4ff4fw@wwwf4f-f4@www/#5#5'&76 764/&"%#!"&5463!248` # \P\w@www4`8  #@  `\P\`@www)4&#!"273276#!"&5463!2& *f4 'w@www`&')4f*@www%5 64'&"3276'7>332#!"&5463!2`'(wƒa8! ,j.( &w@www`4`*'?_`ze<  bw4/*@www-.  6 $$  (r^aaO(_^aa -"'&763!24&#!"3!26#!"&5463!2yB(( @   w@www]#@##   @ @www -#!"'&7624&#!"3!26#!"&5463!2y((@B@u @   w@www###@  @ @www -'&54764&#!"3!26#!"&5463!2@@####@w@wwwB((@@www`%#"'#"&=46;&7#"&=46;632/.#"!2#!!2#!32>?6#  !"'?_  BCbCaf\ + ~2   }0$  q 90r p r%D p u?#!"&=46;#"&=46;54632'.#"!2#!!546;2D a__ g *`-Uh1    ߫}   $^L  4b+"&=.'&?676032654.'.5467546;2'.#"ǟ B{PDg q%%Q{%P46'-N/B).ĝ 9kC< Q 7>W*_x*%K./58`7E%_ ,-3  cVO2")#,)9;J) "!* #VD,'#/&>AX>++"''&=46;267!"&=463!&+"&=463!2+32Ԫ$   pU9ӑ @/*f o  VRfq f=SE!#"&5!"&=463!5!"&=46;&76;2>76;232#!!2#![       % )   "  Jg Uh BW&WX hU g 84&#!!2#!!2#!+"&=#"&=46;5#"&=46;463!2j@jo g|@~vv u n#467!!3'##467!++"'#+"&'#"&=46;'#"&=46;&76;2!6;2!6;232+32QKt# #FNQo!"դѧ !mY Zga~bm] [o"U+, @h h@@X hh @83H\#5"'#"&+73273&#&+5275363534."#22>4.#2>ut 3NtRP*Ho2 Lo@!R(Ozh=,GID2F 8PuE>.'%&TeQ,jm{+>R{?jJrL6V @`7>wmR1q uWei/rr :Vr" $7V4&#"326#"'&76;46;232!5346=#'73#"'&'73267##"&54632BX;4>ID2F +>R{8PuE>.'%&TeQ,jm{?jJrL6 @`rr :Vr3>wmR1q uWei@ \%4&#"326#!"&5463!2+".'&'.5467>767>7>7632!2&%%&&&& &7.' :@$LBWM{#&$h1D!  .I/! Nr&&%%&&&&V?, L=8=9%pEL+%%r@W!<%*',<2(<&L,"r@ \#"&546324&#!"3!26%#!#"'.'.'&'.'.546767>;&%%&&&& &i7qN !/I.  !D1h$&#{MWBL$@: '.&&%%&&&&=XNr%(M&<(2<,'*%<!W@r%%+LEp%9=8=L  +=\d%54#"327354"%###5#5#"'&53327#"'#3632#"'&=4762#3274645"=424'.'&!  7>76#'#3%54'&#"32763##"'&5#327#!"&5463!2BBPJNC'%! B? )#!CC $)  54f"@@ B+,A  A+&+A  ZK35N # J!1331CCC $)w@www2"33FYF~(-%"o4*)$(* (&;;&&9LA3  8334S,;;,WT+<<+T;(\g7x:&&::&&<r%-@www  +=[c}#"'632#542%35!33!3##"'&5#327%54'&#"5#353276%5##"=354'&#"32767654"2 '.'&547>76 3#&'&'3#"'&=47632%#5#"'&53327''RZZ:kid YYY .06 62+YY-06 R[!.'CD''EH$VVX::Y X;:Y fyd/%jG&DC&&CD&O[52. [$C-D..D^^* ly1%=^I86i077S 3 $EWgO%33%OO%35 EEFWt;PP;pt;PP;pqJgTFQ%33&PP%33%R 7>%3!+}{'+"&72'&76;2+"'66;2U &  ( P *'eJ."-dZ-n -'74'&+";27&+";276'56#!"&5463!2~} 7e  ۩w@www"  $Q #'!# @www I-22#!&$/.'.'.'=&7>?>369II ! ' $ !01$$%A' $ ! g  \7@)(7Y   \7@)(7Y @ '5557 ,VWQV.RW=?l%l`~0  !#!#%777 5! R!!XCCfff݀# `,{{{`Og4&"2 &6 $"&462$"&62>7>7>&46.'.'. '.'&7>76 Ԗ HR6L66LGHyU2L  L2UyHHyU2L  L2UyHn X6X  XX ԖԖH6L66L6 L2UyHHyU2L  L2UyHHyU2L n6X  XX  2#!"&54634&"2$4&"2ww@ww||||||w@www||||||| !3 37! $$ n6^55^h ^aaM1^aaP *Cg'.676.7>.'$7>&'.'&'? 7%&'.'.'>767$/u5'&$I7ob?K\[zH,1+.@\7':Yi4&67&'&676'.'>7646&' '7>6'&'&7>7#!"&5463!2PR$++'TJXj7-FC',,&C ."!$28 h /" +p^&+3$ i0(w@www+.i6=Bn \C1XR:#"'jj 8Q.cAj57!? "0D$4" P[ & 2@wwwD"%.5#5>7>;!!76PYhpN!HrD0M C0N#>8\xx: W]oW-X45/%'#.5!5!#"37>#!"&5463!2p>,;$4 5eD+WcEw@wwwK()F ,VhV^9tjA0/@www@#"'&76;46;23   &  ++"&5#"&7632  ^  c  & @#!'&5476!2 &  ^  b '&=!"&=463!546  &    q&8#"'&#"#"5476323276326767q'T1[VA=QQ3qqHih"-bfGw^44O#A?66%CKJA}} !"䒐""A$@C3^q|z=KK?6 lk)  %!%!VVuuu^-m5w}n~7M[264&"264&"2"&546+"&=##"&5'#"&5!467'&766276#"&54632    *<;V<<O@-K<&4'>&4.'.'.'.'.'&6&'.'.6767645.'#.'6&'&7676"&'&627>76'&7>'&'&'&'&766'.7>7676>76&6763>6&'&232.'.6'4."7674.'&#>7626'.'&#"'.'.'&676.67>7>5'&7>.'&'&'&7>7>767&'&67636'.'&67>7>.'.67 \  U7  J#!W! '  " ';%  k )"    '   /7*   I ,6 *&"!   O6* O $.( *.'  .x,  $CN      * 6   7%&&_f& ",VL,G$3@@$+ "  V5 3"  ""#dA++ y0D- %&n 4P'A5j$9E#"c7Y 6" & 8Z(;=I50 ' !!e  R   "+0n?t(-z.'< >R$A"24B@( ~ 9B9, *$        < > ?0D9f?Ae  .(;1.D 4H&.Ct iY% *  7      J  <    W 0%$  ""I! *  D  ,4A'4J" .0f6D4pZ{+*D_wqi;W1G("% %T7F}AG!1#%  JG 3  '.2>Vb%&#'32&'!>?>'&' &>"6&#">&'>26 $$ *b6~#= XP2{&%gx| .W)oOLOsEzG< CK}E $MFD<5+ z^aa$MWM 1>]|YY^D եA<KmE6<" @9I5*^aa>^4./.543232654.#"#".#"32>#"'#"$&547&54632632':XM1h*+D($,/9p`DoC&JV;267676&#!"&=463!267 #!"'&5463!26%8#! &&Z"M>2! ^I 7LRx_@>MN""`=&&*%I},  L7_jj9/%4&#!"3!264&#!"3!26#!"&5463!2  &&&&&&&&19#"'#++"&5#"&5475##"&54763!2"&4628(3- &B..B& -3(8IggI`(8+Ue&.BB.&+8(kk`%-"&5#"&5#"&5#"&5463!2"&4628P8@B\B@B\B@8P8pPPp@`(88(`p.BB.0.BB.(88(Pppͺ!%>&'&#"'.$ $$ ^/(V=$<;$=V).X^aaJ`"(("`J^aa,I4."2>%'%"/'&5%&'&?'&767%476762%6[՛[[՛o ܴ   $ $ " $ $  ՛[[՛[[5` ^ ^ 2` `2 ^ ^ ` 1%#"$54732$%#"$&546$76327668ʴhf킐&^zs,!V[vn) 6<ׂf{z}))Ns3(@ +4&#!"3!2#!"&5463!2#!"&5463!2@&&&f&&&&@&&&&4&&4&@&&&&&&&& `BH+"/##"./#"'.?&5#"&46;'&462!76232!46 `&C6@Bb03eI;:&&&4L4&F Z4&w4) '' 5r&4&&4&&4}G#&/.#./.'&4?63%27>'./&'&7676>767>?>%6})(."2*&@P9A #sGq] #lh<* 46+(  < 5R5"*>%"/ +[>hy  K !/Ui%6&'&676&'&6'.7>%.$76$% $.5476$6?62'.76&&'&676%.76&'..676#"NDQt -okQ//jo_  %&JՂYJA-.-- 9\DtT+X?*<UW3' 26$>>W0 {"F!"E    ^f`$"_]\<`F`FDh>CwlsJ@ ;=?s  :i_^{8+?` ) O`s2RDE58/Kr #"'>7&4$&5mī"#̵$5$"^^W=acE*czk./"&4636$7.'>67.'>65.67>&/>z X^hc^O<q+f$H^XbVS!rȇr?5GD_RV@-FbV=3! G84&3Im<$/6X_D'=NUTL;2KPwtPt=  &ռ ,J~S/#NL,8JsF);??1zIEJpqDIPZXSF6\?5:NR=;.&1 +!"&=!!%!5463!2sQ9Qs***sQNQsBUw wUBFHCCTww%1#"&=!"&=463!54632.  6 $$     ` ?(r^aa    (_^aa%1#!#"'&47632!2.  6 $$   @  ` (r^aa  ?  @  (_^aa/#"'&476324&#!"3!26#!"&5463!2&@& @   w@www& @B@ &  @ @www"&462  >& $$ Ԗ*(r^aaԖԖ (^aa]6#"$54732>%#"'!"&'&7>32'!!!2f:лѪz~u: ((%`V6B^hD%i(]̳ޛ *>6߅r#! 3?^BEa߀#9#36'&632#"'&'&63232#!"&5463!2 Q,&U #+' ;il4L 92<D`w@www`9ܩ6ɽ ]`C477&@wwwD+"&5#"'&=4?5#"'&=4?546;2%6%66546;2  wwwwcB G]B Gty]ty #3C#!+"&5!"&=463!46;2!24&#!"3!26#!"&5463!2@`@`^BB^^B@B^www@w@`@`2@B^^BB^^ww@w'/?P+5#"&547.467&546;532!764'!"+32#323!&ln@ :MM: @nY*Yz--zY*55QDDU9pY-`]]`.X /2I$ t@@/!!/@@3,$,3$p$00&*0&& !P@RV2#"&/#"&/#"&546?#"&546?'&54632%'&54632763276%>S]8T;/M77T7%>ww@ww!"5bBBb// * 8(@(87)(8=%/' #?w@www#~$EE y &L(88e):8(%O r    O?GQaq47&67>&&'&67>&"$32#"#"'654  $&6 $6&$ CoL.*K  Px.* iSƓ i 7J ?~pi{_Я;lLUZ=刈刈_t'<Z :!   @! j`Q7  $ky, Rfk*4LlL=Z=刈&$&546$7%7&'5>]5%w&P?zrSF!| &0 ##!"&5#5!3!3!3!32!546;2!5463) );));;))&&&@@&&&  6 $&727"'%+"'&7&54767%&4762֬>4P t+8?::  ::A W` `EvEEvE<."e$IE&O &EI&{h.`m"&#"&'327>73271[ >+)@ (]:2,C?*%Zx/658:@#N C= E(oE=W'c:#!#"$&6$3 &#"32>7! ڝyy,{ۀہW^F!LC=y:yw߂0H\R%"N^ '&76232762$"&5462"&46274&"&'264&#"'&&#"32$54'>$ $&6$ G>>0yx14J55J5J44J5Fd$?4J55%6E#42F%$fLlLq>>11J44%&4Z%44J54R1F$Z-%45J521Z%F1#:ʎ 9LlL#Qa"'&7622762%"&5462"&546274&#"&'73264&#"'&&#"32654'>#!"&5463!2 55 **.>.-@-R.>.-@-<+*q6- -- 0OpoOxzRrqP6z~{{Prr^aa]054&"#"&5!2654632!#"&57265&'&#".'&'#"&5467%&4>7>3263232654.547'654'63277.'.*#">7?67>?>32#"'7'>3'>3235?KcgA+![,7*  2(-#=  /~[(D?G  |,)"# +)O8,+'6 y{=@0mI#938OAE` -  )y_/FwaH8j7=7?%a % %!?)L J 9=5]~pj  %(1$",I  $@((  +!.S -L__$'-9L 5V+ 6 T+6.8- $ 0 + t |S 16]&#"'&#"67>76'&'&#"67>32764.#"#.32>67>7 $&54>7>7>7rJ@ "kb2)W+ ,5/1   #   Z -!$IOXp7sLCF9vz NAG#/ 5|Հ';RKR/J#=$,9,+$UCS7'2"1  ! / ,   /--ST(::(ep4AM@=I>".)xΤlsY|qK@ %(YQ&N EHv~<Zx'#"&5467&6?2?'&"/.7.546326#"&'&/7264/7'764&"'?>>32.AUpIUxYE.A %%%h% %hJ%D,FZxULs TgxUJrVD %hJ%@/LefL.C %Jh%CV sNUxϠ@.FZyUHpVA %h&%% %Ji%CWpIUybJ/Uy^G,D %Jh%@U sMt UC %hJ%C-KfyEX[_gj&/&'.''67>7>7&'&'&'>76763>7>#&'&'767672'%'7'+"&'&546323267>7%#"'4'6767672,32,+DCCQLDf' % :/d B 4@ }  &!0$?Jfdf-.=6(:!TO? !IG_U% . k*.=; 5gN_X "  ##  292Q41   *6nA;| BS N.  %1$ 6 $nk^ '7GWgw2+"&5463#!"&5463!254&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";26#"&=! B^^BB^^B:FjB^8((`( `(8^BB^^B@B^"vEj^B(8(`(8(/?O_o/?2#!"&5463;26=4&+";26=4&+";26=4&+";26=4&+"54&+";2654&+";2654&+";2654&+";2654&+";2654&#!"3!2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";2654&+";26@&&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`' "&5#"&5&4762!762$"&462B\B@B\BOpP.BB..BB.8$PO広3CQ#".54>32#".546322#"&#"#"54>%".54>32%2#"&54> &X=L|<&X=M{2r_-$$-_rUU%&&5%ő'- "'.546762@FF$@B@$.&,&.]]|q#<<#(BB B%'-%'-'%'-"'%&'"'%.5467%467%62@ll@ll,@GG&!@@@@@@!&+#+#6#+$*`:p:px p=`$>>$&@&@ @&p@ &.A!!"!&2673!"5432!%!254#!5!2654#!%!2#!8Zp?vdΊens6(N[RWu?rt1SrF|iZ@7މoy2IMC~[R yK{T:%,AGK2#!"&5463!!2654'654.#532#532"&5!654&#"327#2#>!!ww@ww~uk'JTMwa| DH> I1q Fj?w@wwwsq*4p9O*¸Z^qh LE "(nz8B M'?"&4624&#"'.'324&#"3267##"&/632632.ʏhhMALR vGhг~~K „yO^   ʏʏВ*LM@!שwwȍde)qrOPqȦs:03=7'.?67'67%'>&%'7%7./6D\$>  "N,?a0#O 1G9'/P(1#00  ($=!F "9|]"RE<6 'o9%8J$\ :\HiTe<?}V#oj? d,6%N#" HlSVY]C =@C4&"2!.#!"4&"2+"&=!"&=#"&546;>3!232^^^Y ^^^`pppp`]ibbi]~^^^e^^^PppPPppP]^^]3;EM2+"&=!"&=#"&546;>;5463!232264&"!.#!"264&" ]`pppp`]ibbi^^^dY !^^^]@PppP@@PppP@]^^] ^^^e^^^ 3$#!#!"&5467!"&47#"&47#"&4762++&2 $$ 2&&&4&&Z4&&##&&4&4&44&m4&m+DP4'&#"32763232674'&!"32763 3264'&$#"32763232> $$ g* o`#ə0#z#l(~̠) -g+^aaF s" +g (* 3#!| #/IK/%*%D= )[^aa !!!'!!77!,/,-a/G t%/;<HTbcq%7.#"32%74'&"32765"/7627#"5'7432#"/7632#"5'7432#"&5'74632 #"/6327#"/6327#"/46329"&/462"&/>21"&/567632#!.547632632  *     X    ^  `    ^  b  c   fu U`59u  4J   l~ ~ F 2    m | O,           ru| u  " )9 $7 $&= $7 $&= $7 $&=  $&=46w`ww`ww`wb`VTEvEEvETVTEvEEvET*VTEvEEvET*EvEEvEEvEEv#^ct#!"&5463!2!&'&!"&5!632#"&'#"/&'&7>766767.76;267674767&5&5&'67.'&'&#3274(8((88((`x c`(8!3;:A0?ݫY   ^U 47D$    74U3I  |L38wtL0`((88(@(8(D 9 8(Q1&(!;  (g- Up~R2(/{E(Xz*Z%(i6CmVo8 #T#!"&5463!2!&'&!"&5!3367653335!3#4.5.'##'&'35(8((88((`x c`(8iFFZcrcZ`((88(@(8(D 9 8(kk" kkJ  ! k#S#!"&5463!2!&'&!"&5!%!5#7>;#!5#35!3#&'&/35!3(8((88((`x c`(8-Kg kL#DCJg  jLD`((88(@(8(D 9 8(jj jjkk kk#8C#!"&5463!2!&'&!"&5!%!5#5327>54&'&#!3#32(8((88((`x c`(8 G]L*COJ?0R\wx48>`((88(@(8(D 9 8(jjRQxk !RY#*2#!"&5463!2!&'&!"&5!!57"&462(8((88((`x c`(8Pppp`((88(@(8(D 9 8(ppp  #*7JR5#5#5#5##!"&5463!2!&'&!"&5##5!"&54765332264&"<(8((88((`x c`(8kޑcO"jKKjK`((88(@(8(D 9 8(SmmS?M&4&&4#9L^#!"&5463!2!&'&!"&5!#"/#"&=46;76276'.'2764'.(8((88((`x c`(8 6ddWW6&44`((88(@(8(D 9 8(. G5{{5]]$5995#3C#!"&5463!2!&'&!"&5!2#!"&5463#"'5632(8((88((`x c`(84LL44LL4l  `((88(@(8(D 9 8(L44LL44L  Z #7K[#!"&5463!2!&'&!"&5!>&'&7!/.?'&6?6.7>'(8((88((`x c`(8` 3  3  3  3 v  ?  `((88(@(8(D 9 8( & & - & &  ?   '6#'. '!67&54632".'654&#"32eaAɢ/PRAids`WXyzOvд:C;A:25@Ң>-05rn`H( ' gQWZc[ -%7' %'-'% %"'&54762[3[MN 3",""3,3"ong$߆]gn$+) ")")" x#W#"&#!+.5467&546326$32327.'#"&5463232654&#"632#".#"oGn\ u_MK'̨|g? CM7MM5,QAAIQqAy{b]BL4PJ9+OABIRo?z.z n6'+s:zcIAC65D*DRRD*wyal@B39E*DRRD*'/7  $&6$ 6277&47' 7'"' 6& 6'lLRRZB|RR>dZZLlLZRR«Z&>«|R ! $&54$7 >54'5PffP牉@s-ff`-c6721>?>././76&/7>?>?>./&31#"$&(@8!IH2hM>'  )-* h'N'!'Og,R"/!YQG54'63&547#5#"=3235#47##6323#324&"26%#!"&5463!2F]kbf$JMM$&N92Z2&`9UW=N9:PO;:dhe\=R +)&')-S99kJ<)UmQ/-Ya^"![Y'(<`X;_L6#)|tWW:;X  #'#3#!"&5463!2) p*xeשw@www0,\8@www9I#"'#"&'&>767&5462#"'.7>32>4."&'&54>32JrO<3>5-&FD(=Gq@C$39aLL²L4 &) @]v q#CO!~󿵂72765'./"#"&'&5 }1R<2" 7MW'$  ;IS7@5sQ@@)R#DvTA ; 0x I)!:> +)C 6.> !-I[4&#"324&#"3264&#"324&#"326&#"#".'7$4$32'#"$&6$32D2)+BB+)3(--(31)+BB+)4'--'4'#!0>R HMŰ9ou7ǖD䣣 R23('3_,--,R23('3_,--,NJ ?uWm%#"'%#"'.5 %&'&7632! ; `u%"(!]#c)(  #"'%#"'.5%&'&76 !  (%##fP_"(!)'+ʼn4I#"$'&6?6332>4.#"#!"&54766$32#!"&=46;46;2z䜬m IwhQQhbF*@&('k@z   _hQнQGB'(&*eozΘ@@`  >. $$ ffff^aafff^aa>"&#"#"&54>7654'&#!"#"&#"#"&54>765'46.'."&54632326323!27654'.5463232632,-,,",:! %]& %@2(/.+*)6! <.$..**"+8#  #Q3,,++#-:#"$$ /:yuxv)%$ /?CG%!5%2#!"&5463!5#5!52#!"&54632#!"&5463#5!5`&&&& &&&&&&&&@&&&&&&&&&&&&%2 &547%#"&632%&546 #"'6\~~\h ~\h\ V V VV%5$4&#"'64'73264&"&#"3272#!"&5463!2}XT==TX}}~>SX}}XS>~}w@www~:xx:~}}Xx9}}9xX}@www/>LXds.327>76 $&6$32762#"/&4762"/&47626+"&46;2'"&=462#"'&4?62E0l,  *"T.D@Yooo@5D [  Z  Z  [ ``[ Z  2 ,l0 (T" .D5@oooY@D, Z  [  [  Z ``EZ  [ 5%!  $&66='&'%77'727'%amlLmf?55>fFtuutFLlLHYC L||L Y˄(E''E*( /?IYiy%+"&=46;2+"&=46;2+"&=46;2+"&=46;2%"&=!#+"&=46;2+"&=46;2+"&=46;2+"&=46;2!54!54>$ +"&=46;2#!"&=@&&@3P > P3&&rrr&&rrr he 4LKM:%%:MKL4WT&&%/9##!"&563!!#!"&5"&5!2!5463!2!5463!2&&&&&&  &&&i@&&@&7'#5&?6262%%o;j|/&jJ%p&j;&i&p/|jţ%Jk%o%  :g"&5462#"&546324&#!"263662>7'&75.''&'&&'&6463!276i~ZYYZ~@OS;+[G[3YUD#o?D&G3I=JyTkBuhNV!WOhuAiSy*'^CC^'*SwwSTvvTSwwSTvvWID\_"[ gq# /3qFr2/ $rg%4 HffHJ4d#!#7!!7!#5!VFNrmNNN N!Y+?Ne%&'&'&7>727>'#&'&'&>2'&'&676'&76$7&'&767>76 '6# <;11x# *# G,T93%/#0vNZ;:8)M:( &C.J}2 %0  ^*  JF &7'X"2LDM" +6 M2+'BQfXV#+] #' L/(eB9  #,8!!!5!!5!5!5!5#26%!!26#!"&5!5&4& &pPPp@@&&@!&@PppP@*  9Q$"&54627"."#"&547>2"'.#"#"&5476$ "'&$ #"&5476$ (}R}hLK NN Ud: xx 8    ,, |2222 MXXM ic,>>,   ̺  '/7?KSck{4&"2$4&"24&"24&"24&"24&"24&"24&"24&"264&"24&#!"3!264&"2#!"&5463!2KjKKjKjKKjKjKKjKKjKKjKjKKjKjKKjKKjKKjKjKKjKLhLLhLKjKKj&&&&KjKKjL44LL44L5jKKjKKjKKjKjKKjKjKKjKjKKjKjKKjKjKKjKjKKjK4LL44LLjKKjK&&&&jKKjK4LL44LL 'E!#"+"&7>76;7676767>'#'"#!"&7>3!2W",&7' #$ &gpf5 O.PqZZdS -V"0kqzTxD!!8p8%'i_F?;kR(` !&)' (2!&6367! &63!2! `B 1LO(+#=)heCQg#s`f4#6q'X|0 -g >IY#6?>7&#!%'.'33#&#"#"/3674'.54636%#"3733#!"&5463!24  : @7vH%hEP{0&<'VFJo1,1.F6A#L44LL44L"% 7x'6 O\JYFw~v^fH$ ! "xdjD"!6`J4LL44LL +3@GXcgqz -<JX{&#"327&76'32>54.#"35#3;5#'#3537+5;3'23764/"+353$4632#"$2#462#"6462""'"&5&5474761256321##%354&'"&#"5#35432354323=#&#"32?4/&54327&#"#"'326'#"=35#5##3327"327'#"'354&3"5#354327&327''"&46327&#"3=#&#"32?"5#354327&3=#&"32?"#3274?67654'&'4/"&#!"&5463!2_gQQh^_~\[[\]_^hQQge<F$$$ !!&&/ !/  !! 00/e&'!"e$   '!!''   8''NgL44LL44LUQghQUk=("  ! =))=2( '! 'L#(>( & DC(>(zL#DzG)<)4LL44LL  BWbjq}+532%+5324&+32763#4&'.546327&#"#"'3265#"&546325&#"32 !264&"2%#'#735#535#535#3'654&+353#!"&5463!29$<=$@?SdO__J-<AA@)7")9,<$.%0*,G3@%)1??.+&((JgfJ*A!&jjjGZYGиwsswPiL>8aA !M77MM77M3! 4erJ]&3YM(, ,%7(#)  ,(@=)M%A20C&Mee(X0&ĖjjjV 8Z8J9N/4$ 8NN88NN  #&:O[ $?b3'7'#3#%54+32%4+324+323'%#5#'#'##337"&##'!!732%#3#3##!"&53733537!572!56373353#'#'#"5#&#!'#'#463!2#"5#"5!&+&+'!!7353273532!2732%#54&+#32#46.+#2#3#3##+53254&".546;#"67+53254&.546;#"#'#'##"54;"&;7335wY-AJF=c(TS)!*RQ+*RQ+Y,B^9^Ft`njUM ') ~PSPRm٘M77Mo7q @)U 8"E(1++NM77Mx378D62W74;9<-A"EA0:A F@1:ؗBf~~""12"4(w$#11#@}}!%+%5(v$:O\zK?* $\amcrVlOO176Nn23266&+"&#"3267;24&+"'&+";27%4&+";2?>23266&+"&#"3267;254+";27#76;2#!"&5463!23%#2%%,,  _3$$2%%M>AL Vb5)LDHeE:< EM j,K'-R M ~M>AR  Vb5)LEHeE:< E J ABI*'! ($rL44LL44Lv%1 %3!x*k $2 %3!;5h n a !(lI;F   rp p8;5h t a !(lI;F ` #k 4LL44LL  2HW[lt#"'5632#6324&'.54327&#"#"&'32767#533275#"=5&#"'#36323#4'&#"'#7532764&"24'&#"327'#"'&'36#!"&5463!2=!9n23BD$ &:BCRM.0AC'0RH`Q03'`.>,&I / * / 8/n-(G@5$ S3=,.B..B02^`o?7je;9G+L44LL44LyE%# Vb;A !p &'F:Aq)%)#orgT$ v2 8)2z948/{ 8AB..B/q?@r<7(g/4LL44LL ?#!"&'24#"&54"&/&6?&5>547&54626=L4@ԕ;U g3 T 2RX='8P8|5 4Ljj U;Ig@   `  "*\(88(]k  &N4#"&54"3 .#"#!"&'7!&7&/&6?&5>547&54626;U gIm*]Z0L4@ԕ=o=CT T 2RX='8P8|5  U;IgXu?bl3@4Ljja`   `  "*\(88(]k/7[%4&+";26%4&+";26%4&+";26!'&'!+#!"&5#"&=463!7>3!2!2@@@@@@0 o`^BB^`5FN(@(NF5@@@u  @LSyuS@%44%,<H#"5432+"=4&#"326=46;2  >. $$ ~Isy9"SgR8vHD w ffff^aam2N+ )H-mF+10*F +fff^aab4&#"32>"#"'&'#"&54632?>;23>5!"3276#"$&6$3 k^?zb=ka`U4J{K_/4^W&  vx :XB0܂ff ) fzzXlz=lapzob35!2BX G@8  ' '=vN$\ff  1 SZz8zX#("/+'547'&4?6276 'D^h  i%5@%[i  h]@]h  i%@5%[i  h^@@)2#"&5476#".5327>OFi-ay~\~;'S{s:D8>)AJfh]F?X{[TC6LlG]v2'"%B];$-o%!2>7>3232>7>322>7>32".'.#"#"&'.#"#"&'.#"#546;!!!!!32#"&54>52#"&54>52#"&54>52-P&+#($P.-P$'#+&PZP&+#"+&P-($P-.P$(#+$P.-P$'#+&P-.P$+#pP@@PpH85K"&ZH85K"&ZH85K"&Z@Pp@@@pMSK5, :&LMSK5, :&LMSK5, :& !!3 ! @@@  #"$$3!!2"jaѻxlalxaaj!!3/"/'62'&63!2'y  `I  yMy `I y'W`#".'.#"32767!"&54>3232654.'&546#&'5&#" 4$%Eӕ;iNL291 ;XxR`f՝Q8TWiWgW:;*:`Qs&?RWXJ8 oNU0 J1F@#) [%6_POQiX(o`_?5"$iʗ\&>bds6aP*< -;iFn* -c1BWg4'.'4.54632#7&'.#"#"'.#"32767'#"&54632326#!"&5463!2#$( 1$6]' !E3P|ad(2S;aF9'EOSej]m] <*rYshpt.#)$78L*khw@wwwB % $/$G6 sP`X):F/fwH1pdlqnmPHuikw_:[9D'@www34."2>$4.#!!2>#!".>3!2QнQQнQQh~wwhfffнQQнQQнQZZQffff#>3!2#!".2>4."fffнQQнQQffffQнQQн ,\!"&?&#"326'3&'!&#"#"'  5467'+#"327#"&463!!'#"&463!2632(#AHs9q ci<= #]$ KjKKjKKjKKjH#j#H&&&KjKKjKg V i jKKjKKjKKjK ..n(([5KK55KK5[poNv<+#"'#"&546;&546$32322$B$22$$*$22$Xڭӯ$22$tX'hs2$ϧkc$22$1c$2F33F3VVT2#$2ԱVT2#$2g#2UU݃ 2$#2UU1݃2 ,u54#"67.632&#"32654'.#"32764.'&$#"7232&'##"&54732654&#"467&5463254632>32#"'&ru&9%" *#͟ O%GR=O&^opC8pP*bY _#$N Pb@6)?+0L15 "4$.Es  5IQ"!@ h "Y7e|J>ziPeneHbIlF>^]@n*9 6[_3#"&54632#.#"32%3#"&54632#.#"326%4&'.'&! ! 7>7>! =39? 6'_ >29? 5'17m-VU--,bW.뮠@Fyu0HC$뮠@Fyu0HC$L= ?? <=! A <`;+"&54&#!+"&5463!2#!"&546;2!26546;2pЇ0pp@Ipp>Sc+"&=46;254&+"&+";2=46;2;2=46;2;2%54&#!";2=;26#!"&5463!2A5DD5A7^6a7MB55B7?5B~```0`rr5A44A5v5AA5f*A``0` !!!! #!"&5463!2ړ7H7jv@vvv':@vvvMUahmrx#"'!"'!#"&547.547.54674&547&54632!62!632!#!627'!%!"67'#77!63!!7357/7'%# %'3/&=&' 5#?&547 6!p4q"""6" 'h*[ |*,@?wAUMpV@˝)Ϳw7({*U%K6=0(M "! O dX$k !! ! b [TDOi @6bxBAݽ5  ɝ:J +3,p x1Fi (R 463!#!"&5%'4&#!"3`а@..@A-XfB$.BB..C} )&54$32&'%&&'67"w`Rd]G{o]>p6sc(@wgmJPAjyYWa͊AZq{HZ:<dv\gx>2ATKn+;"'&#"&#"+6!263 2&#"&#">3267&#">326e~└Ȁ|隚Ν|ū|iyZʬ7Ӕްr|uѥx9[[9jj9ANN+,#ll"BS32fk[/?\%4&+";26%4&+";26%4&+";26%4&+";26%#!"&5467&546326$32]]eeeeee$~i qfN-*#Sjt2"'qCB8!'> !%)-159=AEIMQUY]agkosw{! %! 5!#5#5#5#5#57777????#5!#5!#5!#5!#5!#5!#5!#5#537#5!#5!#5!#5!#5!#55#535353535353%"&546326#"'#32>54.&54>3237.#"Q%%%%%%%%%?iiihOiixiiyiixiiArssrrssr%sssrrssNs%%%%%%%%%%'32#".543232654&#"#"&54654&#"#"&547>326ڞUzrhgrxSПdU 7#"&463!2!2&&4&&&&4&KjKKjKjKKj &&&%&& &&4&&&&4&&&5jKKjKKjKKjK%z 0&4&&3D7&4& %&'S4&"4&"'&"27"&462"&462!2#!"&54>7#"&463!2!2&4&4&4&4KjKKjKjKKj &&&%&& &&4&%&&ے&4"jKKjKKjKKjK%z 0&4&&3D7&4& %& & !'! !%!!!!%"'.763!2o]FooZY@:@!!gf//I62'"/"/"/"/"/"/"/7762762762762762762%"/77627&6?35!5!!3762762'"/"/"/"/"/"/%5#5!4ZSS6SS4SS4SS4SS4SS4SS4ZSS4SS4SS4SS4SS4SS4S-4ZSS4S@4SS4ZSS6SS4SS4SS4SS4SS4S@ZSSSSSSSSSSSSSSZSSSSSSSSSSSSSyZRRR@%:= :+: =RRZSSSSSSSSSSSSSCv!/&'&#""'&#" 32>;232>7>76#!"&54>7'3&547&547>763226323@``` VFaaFV      $. .$     yy .Q5ZE$ ,l*%>>%*>*98(QO!L\p'.'&67'#!##"327&+"&46;2!3'#"&7>;276;2+6267!"'&7&#"(6&#"#"' Dg OOG`n%ELL{@&&Nc,sU&&!Fre&&ss#/,<= #]gL oGkP'r-n&4&2-ir&&?o  4 _5OW! .54>762>7.'.7>+#!"&5#"&5463!2"&462{{BtxG,:`9(0bԿb0(9`:,GxtB&@&&@&K55K`?e==e?1O6# ,  #$  , #6OO&&&&5KK?!"'&'!2673267!'. ."!&54632>321 4q#F""8'go#- #,"tYg>oP$$Po> Zep#)R0+I@$$@I++332++"&=#"&=46;.7>76$  @ ᅪ*r@@r'/2+"&5".4>32!"&=463  &@~[՛[[u˜~gr&`u՛[[՛[~~@r=E32++"&=#"&=46;5&547&'&6;22676;2  >``@``ٱ?E,,=?rH@``@GݧH`jjrBJ463!2+"&=32++"&=#"&=46;5.7676%#"&5   &@~``@``  vXr&@``@+BF`rks463!2+"&=32++"&=#"&=46;5&547'/.?'+"&5463!2+7>6 %#"&5   &@~``@``~4e  0  io@& jV  0  Z9r&@``@Gɞ5o , sp &@k^ , c8~~`r8>KR_32++"&=!+"&=#"&=46;.767666'27&547&#"&'2#" @@ 'Ϋ'sggsww@sgg@@-ssʃl99OOr99FP^l463!2+"&=$'.7>76%#"&=463!2+"&=%#"&54'>%&547.#"254&' &@L?CuГP vY &@;"ޥ5݇ޥ5`&_ڿgwBF@&J_ s&&?%x%xJP\h463!2+"&='32++"&=#"&=46;5.7676632%#"&56'327&7&#"2#" &@L? ߺu``@``} ຒɞueeu9uee&_"|N@``@""|a~lo99r9@9;C2+"&5"/".4>327'&4?627!"&=463  &@Ռ .  N~[՛[[u˜N .  gr&`֌  . Ou՛[[՛[~N  . @r9A'.'&675#"&=46;5"/&4?62"/32+  '֪ \  . 4 .  \r|ݧ憛@\ .    . \@r~9A"/&4?!+"&=##"$7>763546;2!'&4?62  m  - @ݧ憛@& -  @rm4 -  ٮ*   - r+"&5&54>2  @[՛[rdGu՛[[r  ".4>2r[՛[[՛r5՛[[՛[[$2#!37#546375&#"#3!"&5463#22#y/Dz?s!#22#2##2S88 2#V#2L4>32#"&''&5467&5463232>54&#"#"'.Kg&RvgD $ *2% +Z hP=DXZ@7^?1 ۰3O+lh4`M@8'+c+RI2 \ZAhSQ>B>?S2Vhui/,R0+ ZRkmz+>Q2#"'.'&756763232322>4."7 #"'&546n/9bLHG2E"D8_ pdddxO"2xxê_lx2X  !+'5>-pkW[C I I@50Oddd˥Mhfxx^ә #'+/7!5!!5!4&"2!5!4&"24&"2!!! 8P88P 8P88P88P88PP88P8 P88P88P88P8 +N &6 !2#!+"&5!"&=463!46;23!#!"&54>32267632#"_>@`     `  L4Dgy 6Fe=OOU4L>   ` `  4L2y5eud_C(====`L43V &6 #"/#"/&54?'&54?6327632#!"&54>32 7632_>     %%Sy 6Fe=J%>     %65%Sy5eud_C(zz.!6%$!2!!!46;24&"2!54&#!"&&&@ԖV@&&@&&ԖԖ@&3!!! !5!'!53!! #7IeeI7CzCl@@@#2#!"&?.54$3264&"!@մppp((ppp#+/2#!"&?.54$3264&"!264&"!@մ^^^@^^^@((^^^^^^v(#"'%.54632 "'% 632U/@k0G,zD# [k# /tg F Gz  #'#3!) p*xe0,\8T #/DM%2<GQ^lw &'&676676&'&7654&'&&546763"#"'3264&7.>&'%'.767&7667&766747665"'.'&767>3>7&'&'47.'.7676767&76767.'$73>?>67673>#6766666&'&6767.'"'276&67&54&&671&'6757>7&"2654&57>&>&'5#%67>76$7&74>=.''&'&'#'#''&'&'&'65.'&6767.'#%&''&'#2%676765&'&'&7&5&'6.7>&5R4&5S9 W"-J0(/r V"-J0(.)#"6&4pOPppc|o}vQ[60XQW1V  # 5X N"& . ) D>q J:102(z/=f*4!> S5b!%  (!$p8~5..:5I  ~T 4~9p# ! ) & ?()5F 1   d%{v*: @e s|D1d {:*dAA|oYk'&<tuut&v HCXXTR;w 71™ Z*&' 1  9? . $Gv 5k65P.$.`aasa``Z9k'9؋ӗa-*Gl|Me_]`F& OܽsDD!/+``aa``a154&'"&#!!26#!"&5463!2    iLCly5)*Hcelzzlec0hb,,beIVB9@RB9J_L44LL44L44%2"4:I;p!q4bb3p (P`t`P(6EC.7BI64LL44LL  .>$4&'6#".54$ 4.#!"3!2>#!"&5463!2Zjbjj[wٝ]>oӰٯ*-oXL44LL44L')꽽)J)]wL`ֺ۪e4LL44LL;4&#!"3!26#!"&5463!2#54&#!";#"&5463!2  @ ^BB^^B@B^  B^^B@B^`@  MB^^B@B^^>  ^B@B^^5=Um ! !!2#!"&=463!.'!"&=463!>2!2#264&"".54>762".54>762?(``(?b|b?B//B/]]FrdhLhdrF]]FrdhLhdrF@@@(?@@ ?(@9GG9@/B//BaItB!!BtI Ѷ!!ь ItB!!BtI Ѷ!!ь-M32#!"&=46;7&#"&=463!2#>5!!4.'.46ՠ`@`ՠ`MsFFsMMsFFsMojjo@@jj@@<!(!!(!-3?32#!"&=46;7&#"&=463!2+!!64.'#ՠ`@`ՠ`  DqLLqDojjo@@jj@@B>=C-3;32#!"&=46;7&#"&=463!2+!!6.'#ՠ`@`ՠ`UVU96gg6ojjo@@jj@@β**ɍ-G32#!"&=46;7&#"&=463!2#>5!!&'.46ՠ`@`ՠ`MsFFsMkkojjo@@jj@@<!(!33!(!9I2#!"&=4637>7.'!2#!"&=463@b":1P4Y,++,Y4P1:"":1P4Y,++,Y4P1:"b@@@7hVX@K-AA-K@XVh77hVX@K-AA-K@XVh7Aj"#54&#"'54&#"3!26=476=4&#"#54&'&#"#54&'&'2632632#!"&5&=4632>3265K @0.B @0.B#6'&& l @0.B 2' .B A2TA9B;h" d mpPTlLc _4.HK5]0CB.S0CB./#'?&&)$$)0CB. }(AB.z3M2"61d39L/PpuT(Ifc_E`1X"#4&"'&#"3!267654&"#4&"#4&26326#!"&'&5463246326\B B\B&@5K&@"6LB\B B\B sciL}QP%&#"!"3!754?27%>54&#!26=31?>Ijjq,J[j.-tjlV\$B.R1?@B.+?2`$v5K-%5KK5.olRIS+6K5̈$B\B 94E.&ʀ15uE& ԖPjjdXUGJ7!.B P2.B %2@ 7K5(B@KjKj?+fU E,5K~!1.>F.F,Q5*H$b2#!"&=%!"&=463!7!"&'&=4634'&#!">3!!"3!32#!"3!23!26=n$32>32>32#"#.#"#.#"3!27654&#"547654&#"#654&Mye t|]WSSgSY\x{ 70"1i92DU1&=  =&0@c >&/Btd4!*"8K4+"@H@/'= t?_K93-] UlgQQgsW ]#+ i>p&30&VZ&0B/ %3B. "to ){+C4I (  /D0&p0D3[_cg"'&#"3!2676=4&"#54&#"#54&#"#4&'2632632632#!"&'&5463246#!#!#5K)B4J&@#\8P8 @0.B J65K J6k cJ/4qG^\hB2.1!~K5y?^\Vljt-.j[J,qjjI7$?1R.B+.B$`2?gvEo.5KK5%-K6+SIR[&.E49 B\B$5KG#!+"&5!"&=463!2+"&' +"' +"'&5>;2>76;2Y    M .x - N     u  , u ?  LW   #  *:J4'&+326+"'#+"&5463!2  $6& $&6$ UbUI-uu,uuڎLlLAX!Jmf\$ 6uuu,KLlL-[k{276/&'&#"&5463276?6'.#"!276/&'&#"&5463276?6'.#"  $6&  $&6]h - %Lb`J%E 5 ,5R- h - %Lb`J%E 5 ,5R-'uu,uulL/hR    dMLc  NhR   dMLc  N1uuu,LlL@  ' 7 '7 ``H ``H !``H ```H` '%  7' 7'7 ' $&6$ X`(W:,:X`(WLLlLX`(W:BX`(XLlL $ %/9ES[#"&54632$"&4624&"26$4&#"2%#"&462$#"&4632#"32&! 24>  !#"&'.'#"$547.'!6$327&'77'&77N77N'qqqqqPOrqEsttsst}||}uԙ[WQ~,> nP/R U P酛n >,m'77'&77N77N6^Orqqqqqqt棣棣(~|| on[usј^~33pc8{y%cq33dqpf L 54 "2654"'&'"/&477&'.67>326?>< x ,  (-'sI  VCV  Hr'-(  $0@!BHp9[%&!@0$u  ]\\]-$)!IHV D V HI!)$-#36>N"&462."&/.2?2?64/67>&  #!"&5463!2]]]3 $; &|v;$ (CS31 =rM= 4TC(G zw@www]]]($-;,540= sL =45,; @www(2#"$&546327654&#" &#"AZ\@/#%E1/##.1E$![A懇@@\!#21E!6!E13"|! gL&5&'.#4&5!67&'&'5676&'6452>3.'5A5RV[t,G'Q4}-&r! G;>!g12sV&2:#;d=*'5E2/..FD֕71$1>2F!&12,@K r#"&5462>%.#"'&#"#"'>54#".'7654&&5473254&/>7326/632327?&$  $6 $&6$ !&"2&^ u_x^h ;J݃HJǭ qE Dm! M G?̯' %o8 9U(F(ߎLlL&!&!SEm|[n{[<ɪ "p C Di% (K HCέ  pC B m8 @Kނ  HF(LlL "*6%&6$ 7&$5%%6'$2"&4}x3nQH:dΏX e8z' li=! 7So?vM '&7>>7'7>''>76.'6'El:Fg r *t6K3U Z83P)3^I%=9 )<}Jk+C-Wd &U-TE+]Qr-< Q#0 C+M8 3':$ _Q =+If5[ˮ&&SGZoMkܬc#7&#"327#"'&$&546$;#"'654'632ե›fKYYKf¥yͩ䆎L1hvvƚwwkn]*]nlxDLw~?T8bb9SA}+5?F!3267!#"'#"4767%!2$324&#"6327.'!.#"۔c28Ψ-\?@hU0KeFjTlyE3aVsz.b؏W80]TSts<hO_u7bBtSbF/o|V]SHކJ34&#!"3!26#!!2#!"&=463!5!"&5463!2  @ ^B `` B^^B@B^   @ @B^@@^BB^^>3!"&546)2+6'.'.67>76%&F8$.39_0DD40DD0+*M7{L *="# U<-M93#D@U8vk_Y [hD00DD00Dce-JF1 BDN&)@ /1 dy%F#"'&'&'&'&763276?6#"/#"/&54?'&763276"&'&'&5#&763567632#"'&7632654'&#"32>54'&#"'.5463!2#!3>7632#"'&'&#"'&767632yqoq>* 432fba  $B? >B BB AA.-QPPR+ 42 %<ciђ:6& hHGhkG@n`IȌ5 !m(|.mzyPQ-.  je  q>@@?ppgVZE|fb6887a %RB? =B ABBAJvniQP\\PRh!cDS`gΒ 23geFGPHXcCI_ƍ5" n*T.\PQip [*81 / 9@:>t%6#".'.>%6%&7>'.#*.'&676./&'.54>754'&#"%4>327676= >vwd" l "3 /!,+ j2.|%& (N &wh>8X}xc2"W<4<,Z~fdaA`FBIT;hmA<7QC1>[u])  u1V(k1S) - 0 B2* %M ;W(0S[T]I) A 5%R7&&T,Xq&&1X,LΒw%%;#!"&5463!546;2!2!+"&52#!"/&4?63!5! (&&@&&(&&@&&( (  &&@&&@&&&&  #''%#"'&54676%6%% hh @` !   !    #52#"&5476!2#"&5476!2#"'&546        @  @  @    84&"2$4&"2$4&"2#"'&'&7>7.54$ KjKKjKjKKjKjKKjdne4" %!KjKKjKKjKKjKKjKKjK.٫8  !%00C'Z'.W"&462"&462"&462 6?32$6&#"'#"&'5&6&>7>7&54>$ KjKKjKjKKjKjKKjhяW.{+9E=cQdFK1A  0) LlLjKKjKKjKKjKKjKKjKpJ2`[Q?l&٫C58.H(Yee    Y'w(O'R@$#"&#"'>7676327676#" b,XHUmM.U_t,7A3ge z9@xSaQBLb( VU  !!!==w)AU!!77'7'#'#274.#"#32!5'.>537#"76=4>5'.465! KkkK _5 5 #BH1`L I& v6S F!Sr99rS!`` /7K%s}H XV P V  e  Vd/9Q[ $547.546326%>>32"&5%632264&#"64'&""&'&"2>&2654&#";2 P 3>tSU<)tqH+>XX|Wh,:UStW|XX>=X*  ))  +^X^|WX=>X:_.2//a:Ru?  Q%-W|XW>J( =u>XX|WX`  *((*  +2 2X>=XW|E03>$32!>7 '&'&7!6./EUnohiI\0<{ >ORDƚ~˕VƻoR C37J6I`Tb<^M~M8O  5!#!"&!5!!52!5463 ^B@B^`B^^B `B^^"^BB^0;%'#".54>327&$#"32$ !"$&6$3 ##320JUnLnʡ~~&q@tKL}'` - -oxnǑUyl}~~FڎLlLt`(88(   7!' !\W\ d;tZ`_O; }54+";2%54+";2!4&"!4;234;2354;2354>3&546263232632#"&#"26354;2354;2354;2````pp```  !,! -&M<FI(2 ```@PppPpppppp# #   ppppp j#"'&=!;5463!2#!"&=#".'.#!#"&463232>7>;>32#"&'#"!546 %. `@` :,.',-XjjXh-,'.,: kb>PppP>bk .%Z & :k%$> $``6&L')59I"TlԖlT"I95)'L&69GppG9$ >$%k: !+32&#!332 $&6$ ~O88OLlL>pN  iLlL '':Ma4&'#"'.7654.#""'&#"3!267#!"&54676$32#"'.76'&>$#"'.7654'&676mD5)  z{6lP,@KijjOoɎȕ>>[ta) GG 4?a) ll >;_-/ 9GH{zyN@,KԕoN繁y! ?hh>$ D" >â? $ n"&5462'#".54>22654.'&'.54>32#"#*.5./"~~s!m{b6# -SjR,l'(s-6^]Itg))[zxȁZ&+6,4$.X%%Dc* &D~WL}]I0"  YYZvJ@N*CVTR3/A3$#/;'"/fR-,&2-" 7Zr^Na94Rji3.I+ &6W6>N%&60;96@7F6I3+4&#!"3!26%4&#!"3!26 $$ ^aa`@@^aa '7  $ >. %"&546;2#!"&546;2#/a^(^aa(N@@4&#!"3!26 $$ @@^aa`@^aa '  $ >. 7"&5463!2#/a^(n@^aa(N@ %=%#!"'&7!>3!26=!26=!2%"&54&""&546 ##]VTV$KjKKjK$&4&Ԗ&4&>9G!5KK55KK5!&&jj&&#/;Im2+#!"&'#"&463>'.3%4&"26%4&"26%6.326#>;463!232#.+#!"&5#"5KK5sH..Hs5KK5e# )4# %&4&&4&&4&&4&` #4) #%~]eZ&&Ze] E-&&-EKjKj.<<.KjK)#)`"@&&`&&&&`&&)#`)"dXo&&oXG,8&&8!O##!!2#!+"'&7#+"'&7!"'&?63!!"'&?63!6;236;2!2@@8@7 8Q NQ N 8G@ 8GQ NQ N7   8 8  H H  k%  ".>2I20]@]@oo@@oo㔕a22]]p^|11|99|11|(%7'7' ' 7T dltl)qnluul)1$4&"24&"2 &6 +"&5476;2 &6 LhLLhLLhLLhL>  &   &`>hLLhLLhLLhL>&&>G  .7)1!62 1!62he220e22> v +4 [d+ d 135#5&'72!5!#"&'"'#"$547&54$ Eh`X(cYz:L:zYc\$_K`Pa}fiXXiޝfa  (+.>#5#5!5!5!54&+'#"3!267!7!#!"&5463!2U``'    jjV>(>VV>>Vq  ( ^(>VV>>VV=&'&'&'&76'&'&.' #.h8"$Y ''>eX5, ,PtsK25MRLqS;:.K'5R ChhRt(+e^TTu B"$:2~<2HpwTT V/7GWg. %&32?673327>/.'676$4&"2 $&6$   $6& $&6$ d -- m  ,6*6,  mKjKKjoooKzz8zzȎLlLU4>>4-. YG0 )xx) 0GYޞ .jKKjKqoooolzzz80LlLD/7H#"'.7'654&#"'67'.6?>%"&46227#".547|D,=),9#7[͑fx!X: D$ +s)hhijZt<F/*8C,q؜e\r,WBX/C2hhh=tXm>NZ+"&=46;2+"&=4>7>54&#"#"/.7632  >. $$ p=+& 35,W48'3  l zffff^aaP2P: D#;$# $*;? R Cfff^aa'Y >O`"&5462&'.'.76.5632.'#&'.'&6?65\\[( | r [A@[[@A#2#  7* <Y$  +}"(  q87] F  _1 )    #1Ke34&+326+"&=!#!"&763!2#!"&5463!2#>?4.'3#>?4.'3#>?4.'3Xe`64[l7  , L; =+3&98&+)>>+3&98&+)>=+3&88&+)> Wj|r >Q$~d $kaw+-wi[[\;/xgY $kaw+-wi[[\;/xgY $kaw+-wi[[\;/xgYJ\m4.'.'&#"#"'.'&47>7632327>7>54&'&#"327>"&47654'&462"'&476'&462"'&47>&'&462i$ $^"  %%  "^$ $W "@9O?1&&18?t@" W&%%&4KK6pp&46ZaaZ&4mttm ^x -  - x^ = /U7C kkz'[$ =&5%54'4&KK4r7>54 "&54>2"&462%"&54&#""&546 %#"&'&'.7>#"'&'.7>&4&&4&4&&4SZ&4&&44$#&&&j3$"('$&4&[՛[&4&&4F&4&]\&4&$  !D4%  ,\44&&4&4&&4&-Z4&&4&;cX/)#&>B)&4&j9aU0'.4a7&&u՛[[4&&4&@&&]]&&Ώ0 u40 )4#g&'.#"32676%4/&#"326'&#"2632#2+&'%#"'&6?676676632%#"'&6767#"&'&6767#"'.7>327"#"&'&6763"'.7>;7632;>%5K$ "0%>s$ "0%>;;>%5KVL#>H30 \($$(\( єyO2F/{(?0(TK.5sg$ єy#-F/{$70(TK.5sg$L#>H30 \($$(\#(@5"'K58!'"58!'"55"'K#dS$K K$Sdx#@1 w d>N;ET0((? - 2K|1 wd#N;ET0$(? - 2K$#dS$K K$SdxDN\2654& 265462"2654 #"32654>7>54."/&47&'?62 &4&&4&h՛[&4&r$'("$3j&&&#$4[ " @ GB[ "&&Β&&][u&&7a4.'0Ua9j&4&)B>&#)/Xc;u՛ "  " Gi[ Xh#"&54676324&'&#"'>54#"32#"54>54'.#"32>7>767632326#!"&5463!2b )   :4FDN  [1,^JK-*E#9gWRY vm0O w@wwwC22 c@X&!9{MA_"S4b// DR"XljPY < @www%e4.#"32>7676#'.#"#"&54>3232>754&*#"&54>763 >32 ''il$E/  @P@ ^`'W6&!.. ! -P5+ E{n46vLeVz:,SN/ M5M[  ]$[^5iC'2H&!(?]v`* l b$9> =R2 #"&5467%!"&7>3-.7>;%.7>322326/.76/.'&6766/&/&#"&676 &676&6766/&672? =1( H/ '96&@)9<')29% &06##$ J 0 7j)5@"*3%"!M %#K"%Ne 8)'8_(9./=*%8!Q #P"\Q#N&a)<9bR]mp%"'.'&54>76%&54763263 #"/7#"'#"&/%$%322654&#"%'OV9  nt  |\d ϓ[nt  |@D:) ;98'+| j," 41CH^nVz(~R 9\'  r  @L@  @w46HI(+C ,55, f[op@\j;(zV~i/5O#"'&54>32&#" 654'67'"'>54''&'"'6767&546767>7蒓`V BMR B9)̟!SH-77IXmSMH*k#".o;^J qןד>@YM $bKd ү[E";Kx%^6;%T,U:im=Mk).DT4'"&5463267&#" 6;64'.'4'>732676%#!"&5463!2),蛜s5-54&#"#"'654'.#"#"&#"3263232>3232>76 $$ Cf'/'% ( $UL ( #'/'@ 3#@,G)+H+@#3 ^aaX@ _O#NW#O_ .* ##(^aaq[632632#"&#"#".'&#"#".'&54767>7654.54632327&547>P9 B6?K? %O4T% >6>Z64Y=6>%S4N$ ?L?4B @{:y/$ ,'R! F! 8% #)(()#%: !F Q'+%0z:zO_4'.'&54>54&#"#"'654'.#"#"&#"3263232>3232>76#!"&5463!2Cf'.'% ( $VM  ) #'.'@ 3 #A,G)+H+A# 4 w@wwwXA  ?4N$NW&M&L  /* ## + @www O$>?>762'&#"./454327327>7> EpB5 3FAP/h\/NGSL  RP* m95F84f&3Ga4B|wB.\FI*/.?&,5~K % & Y."7n< "-I.M`{ARwJ!FX^dj''''"'7&'7&'7&'7&547'67'67'67'63277774$#"32$   *'ֱ,?g=OO&L&NJBg;1''ֱ.=gCIM $'&&NJBg=.%w؝\\w Ioo<<-NIDg=/%(ײ+AhEHO*"#*OICh=/'(ֲ/=h>ON.]xwڝ]7e[@)6!!"3#"&546%3567654'3!67!4&'7Sgny]K-#75LSl>9V%cPe}&Hn_HȌ=UoLQ1!45647UC" !-9[nx"&46254&"326754&"326754&"26754&"26#".547632632626326'4#"#"54732764&"264.#"327632>#"'"'#"'#"&5#"'67&'327&'&54>3267>7>7>32632632T"8""8)<())(<))))<))<))<))<) Tد{ՐRhx=8 78 n 81 pH_6Soc F@b@?d?uKbM70[f5Y$35KUC<:[;+8 n 87 8/8Zlv]64qE 'YK0-AlB; W#;WS9 &(#-7Z://:/Tr++r,,r++r,,r++r,,r++r,,ʠgxXVעe9222222^KVvF02OO23OO`lF;mhj84DroB@r+@222222C0DP`.r8h9~T4.&o@9 1P%14'!3#"&46327&#"326%35#5##33 $$  }Pcc]321IUΠ?LL?cc4MX &04;0XpD[[DpD,)&&Q 9V\26&".'&'&6?.#"#26327677>'32>&3#'&+"?626&"#!'.'!"&5463!>;26;2!2P P  92#.}SP9::%L \B )spN/9oJ5  !+D`]BgY9+,9% Pk 4P P &NnF!_7*}B<{o0&&B;*<@$ucRRc#@16#37c&@@@ J"@*4^`ED B o/8927 *@OLC!T!323X$BJ@@@&AS 0C 59" 'D/&&D4 88 $5A&%O#!"&547>7>2$7>/.".'&'&2>^B@B^ >FFzn_0P:P2\nzFF> R & p^1P:P1^ & R P2NMJMQ0Rr.B^^B 7:5]yPH!%%"FPy]5:7 = 4 QH!%%!Ht 4 =<"-/ ?1Pp+".'.'.?>;2>7$76&'&%.+"3!26#!"&54767>;2' +~'*OJ%%JN,&x' % ^M,EE,M7 ZE[P*FF*P:5  ^B@B^){$.MK%%KM.$+X)o3 "a 22!] 4  I>"">,&S8JB##B12 ` `B^^B8&ra#11#$R&  "&.2v%/%''%/%7%7'%7'/#&5'&&?&'&?&'&7%27674?6J" 0<=_gNU?DfuYGb7=^H^` =v~yT3GDPO 4Fѭqi_w\ހ!1uS%V_-d 1=U{J8n~r'U4.#".'"3!264&"26+#!"&5463!232+32+32 0P373/./373P0 T=@=T֙֙|`^B@B^^BB^`````*9deG-! !-Ged9IaallkOB^^BB^^B +Yi"&54622#!"&54>;2>+32+32+#!"&5463!2324&#!"3!26֙֙0.I/ OBBO -Q52-)&)-2 ``  ``  `^B@B^^BB^`  @   |kkl"=IYL)CggC0[jM4      B^^BB^^B @  @ !1AQu4.#".'"3!24&"254&#!"3!2654&#!"3!2654&#!"3!26#!54&+"!54&+"!"&5463!2)P90,***,09P)J66S"@8@^B@@B^^BB^Ukc9 9ckU?@@88 @@N@B^````^BB^^!1AQu#!"&4>32>72"&462#!"&=463!25#!"&=463!25#!"&=463!24&#!"3!546;2!546;2!26#!"&5463!2J66J)P90,***,09P)"@8@ @  `@@` ^B@B^^BB^ՀUUkc9 9c`@@88@@2  @ ````@B^^BB^^(%.'"&' $&  #"$&6$ wCιCwjJ~J>LlLśJSSJ͛>6LlL$,  $&6654&$ 3 72&&  lLmzzBl>KlLGzzG>'7#!"&54>7&54>2  62654' '3/U]B,ȍ,B]U/OQнQ>+X}}X0bӃۚӅb0}hQQh>ff#=#!"&4>3272"&462!3!26#!"&5463!;26=!2J66J)Q8PP8Q)  ^B@B^^B``B^VVVld9KK9d` @B^^BB^``^+;K[eu4.#"'"3!264&"254&#!"3!2654&#!"3!26%54&+";2654&#!"3!26!54&#!"!#!"&5463!2"D/@@/D"?,,?pppp@@@@^B@B^^BB^D6]W2@@2W]67MMppp@@@@@@@@n`@B^^BB^^+;K[eu#!"&54>3272"&462#!"&=463!2%#!"&=463!2+"&=46;25#!"&=463!2!3!26#!"&5463!2?,V,?"D/@@/D"pppp@@@  ^B@B^^BB^D7MM76]W2@@2W]֠ppp@@@@@@@@` @B^^BB^^A#"327.#"'63263#".'#"$&546$32326J9"65I).!1iCCu +I\Gw\B!al݇yǙV/]:=B>9+32%#!"&5463!2#"&54>54'&#"#"54654'.#"#"'.54>54'&'&543232654&432#"&54>764&'&'.54632  ?c'p& ?b1w{2V ?#&#9&CY' &.&#+B : &65&*2w1GF1)2<)<'  ( BH=ӊ:NT :O )4:i   F~b` e!}U3i?fRUX|'&'&Ic&Q  *2U.L6* / L:90%>..>%b>+ +z7ymlw45)0 33J@0!! TFL P]=GS -kwm  !*(%6&692? $&6$  '   al@lLlL,& EC h$LlL /37;%"&546734&'4&" 67 54746 #5#5#5ppF::FDFNV^fnv~"/&4?.7&#"!4>3267622"&4"&46262"&42"&4462"$2"&42"&4"&46262"&4"&46262"&42"&4$2"&42"&42"&4  R ,H8JfjQhjG^R,  !4&&4&Z4&&4&4&&4&4&&4&&4&&44&&4&4&&4&Z4&&4&4&&4&4&&4&4&&4&4&&4&&4&&4&Z4&&4&Z4&&4&  R  ,[cGjhQRJ'A, &4&&4Z&4&&4Z&4&&4Z&4&&444&&4&&4&&4Z&4&&4Z&4&&4Z&4&&4&4&&4Z&4&&4Z&4&&4&&4&&4Z&4&&4Z&4&&4%-5=EM}+"&=#!"'+"&=&="&4626"&462&"&462"&462&"&462&"&462#!"&=46;4632676/&?.7&#"!2"&462&"&462&"&462"&462&"&462&"&462"&462&"&462"&462@?AA? @ @R...R@`jlL.h) * * $ %35K.....uvnu....@@jN  * * .t2#K5..R..R. @Hq '&'&54 &7676767654$'.766$76"&462&'&'&7>54.'.7>76ȵ|_ğyv/ۃ⃺k] :Buq CA _kނXVobZZbnW|V 0  Q2- l}O  / :1z q%zG 4( 6Roa ą\< )4 J}%!!#!"&5463!2^B@B^^BB^`@B^^BB^^%#!"&=463!2^B@B^^BB^B^^BB^^ &))!32#!#!"&5463!463!2`B^^B^B@B^^B`^BB^^B@B^B^^BB^`B^^#3%764/764/&"'&"2?2#!"&5463!2    s^B@B^^BB^ג     @B^^BB^^#'7"/"/&4?'&4?62762!!%#!"&5463!2     ^B@B^^BB^    `@B^^BB^^ ! $&6$ .2r`LlLf4LlL#.C&>"'&4762"/&4?62'"'&4762%'.>6.'.>6'>/>76&'&.'&7&'">?4'.677>7.>37654'&'67>776 $&6$  ( 4Z# # & # # & y"6&.JM@& "(XE* $+8 jT?3#'.'&!3!2>?3.'#!57>7'./5!27#'.#!"g%%D-!gg<6WWZe#1=/2*]Y3-,C1 /Dx] VFIq-HD2NK '>*%R= f 07=. f D]\|yu,0>Seu#2#"'&5<>323#3#&'#334'."#"+236'&54.#"5#37326#!"&5463!2 <  zzj k-L+ )[$8=".un/2 ^B@B^^BB^5cy    (ݔI(8?C (3> #"($=@B^^BB^^0K S&'.'&'./674&$#">&>?>'76'# "&#./.'7676767>76$w .~kuBR] T%z+",|ޟj<)(!( ~ˣzF8"{%%#5)}''xJF0"H[$%EJ#% .Gk29(B13"?@ S)5" #9dmW";L65RA0@T.$}i`:f3A%% BM<$q:)BD aa%`]A &c| Ms!  Z 2}i[ F&** < ʣsc"J<&NsF% 0@Wm6&'.6$.7>7 $76".4>2., &>6'"'&7>=GV:e #:$?+% q4g &3hT`ZtQмQQмpAP1LK!:< }҈`dlb,9'  %%($! a3)W)x  оQQоQQcQǡ-җe)Us2XD\ϼYd /?O_o#"=#"=4;543#"=#"=4;543#"=#"=4;543#"=#"=4;543#"=#"=4;543%#!"&5463!2++532325++532325++532325++532325++53232p00pp00pp00pp00pp008((88(@(80pp00pp00pp00pp00pp0     @(88((88     /Q/&'%&/"&=.6?&?&'&6?'.>-#".6?'.>'&6'.>54627>%>76#"'% %6 27 2G f!)p&4&p)!f G2 72  *6 " 47 2G f!)p&4&p)!f G2 72 " 6* !k 3 j&3 %,*&&ր*9% 3&j 3 k!./!>>$,*!k 3.j&3 %Ԝ9*&&ր*ǜ,% 3&j 3 k!*,$>>!/.&6.'&$ &76$76$PutۥiPuGxy Զ[xy -_v١eNuv١e =uʦ[t78X &6# #'7-'%'&$  $6 $&6$ 31NE0gR=|||">"LlL^v!1f2iЂwgfZQQ^>"||||wLlL &ZXblw.'&>'&'&".'.'&&'&'&7>767>67>7626&'&>&'&>'.7>.676'&'&'&'.67.>7>6&'&676&'&676.676&'&>&'&676'.>6/4-LJg-   $  6)j2%+QF)b3FSP 21DK2AW ") ")$? ? 8A& AE5lZm= gG2Sw*&>$5jD GHyX/4F r 1  1""!l=6> 6 ,5./'e    .*|Ed! u & &%& &5d ))66 @ C& 8B @qL?P^7 G-hI[q:"T6 ,6 &/`  L wQ'   A ^   "  $& _  y  * <Copyright Dave Gandy 2016. All rights reserved.Copyright Dave Gandy 2016. All rights reserved.FontAwesomeFontAwesomeRegularRegularFONTLAB:OTFEXPORTFONTLAB:OTFEXPORTFontAwesomeFontAwesomeVersion 4.7.0 2016Version 4.7.0 2016FontAwesomeFontAwesomePlease refer to the Copyright section for the font trademark attribution notices.Please refer to the Copyright section for the font trademark attribution notices.Fort AwesomeFort AwesomeDave GandyDave Gandyhttp://fontawesome.iohttp://fontawesome.iohttp://fontawesome.io/license/http://fontawesome.io/license/      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab cdefghijklmnopqrstuvwxyz{|}~"      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~glassmusicsearchenvelopeheartstar star_emptyuserfilmth_largethth_listokremovezoom_inzoom_outoffsignalcogtrashhomefile_alttimeroad download_altdownloaduploadinbox play_circlerepeatrefreshlist_altlockflag headphones volume_off volume_down volume_upqrcodebarcodetagtagsbookbookmarkprintcamerafontbolditalic text_height text_width align_left align_center align_right align_justifylist indent_left indent_rightfacetime_videopicturepencil map_markeradjusttinteditsharecheckmove step_backward fast_backwardbackwardplaypausestopforward fast_forward step_forwardeject chevron_left chevron_right plus_sign minus_sign remove_signok_sign question_sign info_sign screenshot remove_circle ok_circle ban_circle arrow_left arrow_rightarrow_up arrow_down share_alt resize_full resize_smallexclamation_signgiftleaffireeye_open eye_close warning_signplanecalendarrandomcommentmagnet chevron_up chevron_downretweet shopping_cart folder_close folder_openresize_verticalresize_horizontal bar_chart twitter_sign facebook_sign camera_retrokeycogscomments thumbs_up_altthumbs_down_alt star_half heart_emptysignout linkedin_signpushpin external_linksignintrophy github_sign upload_altlemonphone check_emptybookmark_empty phone_signtwitterfacebookgithubunlock credit_cardrsshddbullhornbell certificate hand_right hand_lefthand_up hand_downcircle_arrow_leftcircle_arrow_rightcircle_arrow_upcircle_arrow_downglobewrenchtasksfilter briefcase fullscreengrouplinkcloudbeakercutcopy paper_clipsave sign_blankreorderulol strikethrough underlinetablemagictruck pinterestpinterest_signgoogle_plus_sign google_plusmoney caret_downcaret_up caret_left caret_rightcolumnssort sort_downsort_up envelope_altlinkedinundolegal dashboard comment_alt comments_altboltsitemapumbrellapaste light_bulbexchangecloud_download cloud_uploaduser_md stethoscopesuitcasebell_altcoffeefood file_text_altbuildinghospital ambulancemedkit fighter_jetbeerh_signf0fedouble_angle_leftdouble_angle_rightdouble_angle_updouble_angle_down angle_left angle_rightangle_up angle_downdesktoplaptoptablet mobile_phone circle_blank quote_left quote_rightspinnercirclereply github_altfolder_close_altfolder_open_alt expand_alt collapse_altsmilefrownmehgamepadkeyboardflag_altflag_checkeredterminalcode reply_allstar_half_emptylocation_arrowcrop code_forkunlink_279 exclamation superscript subscript_283 puzzle_piece microphonemicrophone_offshieldcalendar_emptyfire_extinguisherrocketmaxcdnchevron_sign_leftchevron_sign_rightchevron_sign_upchevron_sign_downhtml5css3anchor unlock_altbullseyeellipsis_horizontalellipsis_vertical_303 play_signticketminus_sign_alt check_minuslevel_up level_down check_sign edit_sign_312 share_signcompasscollapse collapse_top_317eurgbpusdinrjpyrubkrwbtcfile file_textsort_by_alphabet_329sort_by_attributessort_by_attributes_alt sort_by_ordersort_by_order_alt_334_335 youtube_signyoutubexing xing_sign youtube_playdropbox stackexchange instagramflickradnf171bitbucket_signtumblr tumblr_signlong_arrow_down long_arrow_uplong_arrow_leftlong_arrow_rightwindowsandroidlinuxdribbleskype foursquaretrellofemalemalegittipsun_366archivebugvkweiborenren_372stack_exchange_374arrow_circle_alt_left_376dot_circle_alt_378 vimeo_square_380 plus_square_o_382_383_384_385_386_387_388_389uniF1A0f1a1_392_393f1a4_395_396_397_398_399_400f1ab_402_403_404uniF1B1_406_407_408_409_410_411_412_413_414_415_416_417_418_419uniF1C0uniF1C1_422_423_424_425_426_427_428_429_430_431_432_433_434uniF1D0uniF1D1uniF1D2_438_439uniF1D5uniF1D6uniF1D7_443_444_445_446_447_448_449uniF1E0_451_452_453_454_455_456_457_458_459_460_461_462_463_464uniF1F0_466_467f1f3_469_470_471_472_473_474_475_476f1fc_478_479_480_481_482_483_484_485_486_487_488_489_490_491_492_493_494f210_496f212_498_499_500_501_502_503_504_505_506_507_508_509venus_511_512_513_514_515_516_517_518_519_520_521_522_523_524_525_526_527_528_529_530_531_532_533_534_535_536_537_538_539_540_541_542_543_544_545_546_547_548_549_550_551_552_553_554_555_556_557_558_559_560_561_562_563_564_565_566_567_568_569f260f261_572f263_574_575_576_577_578_579_580_581_582_583_584_585_586_587_588_589_590_591_592_593_594_595_596_597_598f27euniF280uniF281_602_603_604uniF285uniF286_607_608_609_610_611_612_613_614_615_616_617_618_619_620_621_622_623_624_625_626_627_628_629uniF2A0uniF2A1uniF2A2uniF2A3uniF2A4uniF2A5uniF2A6uniF2A7uniF2A8uniF2A9uniF2AAuniF2ABuniF2ACuniF2ADuniF2AEuniF2B0uniF2B1uniF2B2uniF2B3uniF2B4uniF2B5uniF2B6uniF2B7uniF2B8uniF2B9uniF2BAuniF2BBuniF2BCuniF2BDuniF2BEuniF2C0uniF2C1uniF2C2uniF2C3uniF2C4uniF2C5uniF2C6uniF2C7uniF2C8uniF2C9uniF2CAuniF2CBuniF2CCuniF2CDuniF2CEuniF2D0uniF2D1uniF2D2uniF2D3uniF2D4uniF2D5uniF2D6uniF2D7uniF2D8uniF2D9uniF2DAuniF2DBuniF2DCuniF2DDuniF2DEuniF2E0uniF2E1uniF2E2uniF2E3uniF2E4uniF2E5uniF2E6uniF2E7_698uniF2E9uniF2EAuniF2EBuniF2ECuniF2EDuniF2EE=O<01hdoit-0.36.0/doc/_static/vendor/font-awesome/fonts/fontawesome-webfont.woff000066400000000000000000002773501423054503100266530ustar00rootroot00000000000000wOFF~ FFTM0kGGDEFL OS/2l>`2z@cmapi :gaspglyf _yLMheadb36-hheab$ hmtxb Eylocae \maxpl ,namemD㗋posto`u=O<01hxc`d``b `b`d`d:$Y< xc`fdbʢb l |6F0#Fnx͒Jqgje>"D>{EO >,"u^[[[jos_M%:0g80B.Lszðפ 1YlKWvest)Mk^Zֵ֪m׉Θbk̳26>'YҖjukZۺgm2 (4-iEkЖv}XB Y``c9ZJV5eY߆6G ΂`3| 6[uIpn-[pL0Lp;׸%8o>F8 G8`Wί"E^_=(K,FK+ybx TՕ0o}{uuuwUWիnnjmz-nvEEAAJ!*(hD2c%FʦEbb6$&7߹UUW7 tw{98m8bI ڃ݌7 SEG!3j㔐=w;P^IA;RRnkLS.)o8G([)9O,,AtS h yujZupPGxN on{ho2AD-r]u5e^dMX8=r5ͻ^Q\~2V0 o0kC qA跍 G< 9v`|NXWI:"'aW޺O=}k#"7e %Vs~-y$ŵXw&'q.n.EK#JDڝn봽7=|wL:Ӎ2vmrRv:=0P@DۓVZ7eOd7HMSY|[of'BL}ƷҗV^+{W=uҤ֦='j,| ;vAo=0q8"I³8yZ6Ǵo9q< i3k1%& uk {H}@΁W—^qԷ4;gg7Ny/ qPOЌL4q,ԇ"Sv=jL /UjC-woȍnj̮{j\ vEk z>pn=^=ajID(෠quF;э5֮s7 ;QC7U[׈yZIۘػ*!$ dⵄŖ-ˇ?{mf6po~mԽwoG6Moza--m#]?]?Vkzܥܵ.>)9NH%&T/ _IAxOB]8(.v)G=HPSUP>fFE-GGs|'?~zI*R|[` -V'ݙGP3b'\RI̞#n;W ٟDTѹb80^s6,rȥ ism15kk,}qWȝ;tseYqqC/0q|> 3W/ըsF"sIoAHI 8C„ w~@ _(]h=r9p! ;H-[Ifw;%=d꯵bmH)k=o\hEi 7i:-!mn:`[G]GE,;syH62ƈs՗:I@^\wOVõ<g?]Y{?qKgH[X&tdn[,Z!H6#=nݳ;OWUG4]]6ٰp7[aM5PB]?4P呂7o\!׺ߜؤ 2>8/p2h@k~ھB~a[r=Pr8SescF ӗ S#P|0z'zS)8aFBFE VrJ(EfDpU\'h4P jd3}CvfM}Zlf,.pj1tYj2lƗ,U<:zt[%Y!1vMfrc:_n"7zwvm zuidtO.3Ku =.#Cjn(,THu_Z 6qhhP4#JH%jt3M)#zzdt1Dn~9/ȋB@NV?p'r f: ;bBQHb$h3CG|#v2ydm)esvw~٬fp~DG r 0^XzˣՇcl& \`\8HHa IC?6:5H;lވ4C&\FjԬ,|MCݔ/f8ܮ2 .ҍl _/AkTVΝg ~T΂<`2Q&;XAW@@gj{j, suuE ֟:A 8,&ռ }|b0lFQ$px=4ddm7nru"N:O u^x@񝂍CG*%F>Tm?2.opˮ1r\T١K+L؜cn:8qyN\Dvj[ܦDy/*=H [0l8=`Dd&76tOd٧,崅v2+׷ TU[NHN8W|fG{ܘlT_Z1 8j `Ar㼌` h *b #ռBj0s$n^7w $Gɡ;N .A>3;My?zpͥΙ4aqp҃GFw|]֯!ؾbvq8e+)h.,U~4]h.P4s)+kqD2uϸuE3 V⭯ҟfS8/D]5ޖ*xWGj}l&klnçiPv'6#(%)>qEo6U+6ŋ8ۢlޏ> `Mn''zB-t/ꬱ3ik3 55Z 1ao|+ őm 0$YəOa1ag9up9Gת+b=H߀Q1hT]ҒQ^?s9ػ lB|4TNYBL, g#5A㉐=!7~=/X]WuwZW避[ꞞWd==Bm®ҏ΋v?$ E# L!7ط!TRRI4)H#l*:#H.)pӇ źRMB=ƅ(ǂ͵˥>A,_2%5pyn6/Mbt,L֮l+9QGb]*D; {PZ!*U1|s{"3\gGχyG:-nQg7`ԏ3xAx%ÏUXMZ&HX9>osGa '!lü|EW-ebbxsY06E>)VH ߰}V=G~Ykh/;ۇ0{4.c\h`5 FA5Tg[4#So3yuy=<'j{ hNk6 @1c/5 -T:`YX]g~ilp!e>1x06?eoAsb̪fyb3@B߂Yq?;m)h4skP UfW62c>8F(t*GC ym srp? ICY:ϻ&͜99TY-k%)@|FFh9*(RtKǻTXM-IP.%C"?,+ˆ= >tUgQWw#Υ7 ݋[P ޮ'j7 7̗9ZI SO4YkDE͂B~`Ig;mu֢zSg)rE܉=mK9ZD]4~7߉R6Hۂ(ji!BldpӜ^zz拾gF:qꢝkWl/СuX2rTsBנͫڂt}}ƶ_5 k4 A;oHLϹ)z.quAzyxjk5F-@lҙcڗҗ\6= O]9/5ڔ볝\tOCT3f(i ]w PiQwγ=JߌvGޮy[[,Et&QocÂyb66kMK|֋$Yz%P(^87DrK`%5.: Ďx=mnًm]Ю&2G(-@Q7xu3%@p~нt S]=)AG AVg; *=$mz -|_EZˢk<5U5fFIj`=H})0~F,"N6k"}ṒkT"$mZPc',ϛtzՅ];+j +NG>K#h-zp6\;yb~9.m \=qrqü=fS 6u(؍3#0  :Nz{SM]"`R .Cr`-U{낍znq tx ic+Ԛ:3Y㳙N*aVP `1Qb@fc^X9̼ܶjtҜY ӂhھ3 ijs+\8Tvi|Q< v߹c81-t\16GInJ:̇hX Gr+4Hjv4l!,cC54{ٱ4dR~p*;9nC%d}dA 4Q8iOi TgdulUSAq$.j6U;MǶۏێۏj9JDvAFbmLOI=`jf:>IǁJ! 6Txưqn̓S9ĀM|!ґ8X)hͅͳ(,ӌ2+lD3Qɕp$`Pt[ DV2opo%xZ)n:p4N)F ՆtT7Mu`8P*r >(O^tXi(M4! t(>hcU<@ܦç$M'(J׳Q܃<8Vjj7P?Ͼ;_!Q.h|:B)Ӓxܘs_d9aN=.WO.\|_O&tk.".Dp53͓ 6`8IuKjk/wiUSusUlr ̥;ѠMe`TB&n¦\ g2pd[0Ovz I'm%41}@€:įZ/r @1m8_.WRlv(F5Aս~]*@Qؿ VgM܊:MʞQZ㖵. HfJwKIA\f7zl}5VzG Ɛ u̻vߋaɰZ(S6W z7ek[j #6[6iSڣn@d`[}i]<{bN&kG[Q`Ek$|'GOR4: yX1dhz3TʷL-3DG%Z b锥3I陌R^cy,3P!@ieNq좀FS'}@4шÏ~*T(PY+=!?}>Ю+w*3Usƽ i[9a\uWeY5 +,iK\ʚe<zKC&Hdbktݩ7!;BTR@J vKU8bUH^Q;Okb%[QHO 9谉0r0}U>ʔV5^ܵ}ecFmۈrqLEl "I5ڦfU2cW+O, MJ񝁧6y?*0&Nݚxq?)>e( @qTVx>sjAi2W@WU{LГK^ A'96&E[ h8J*X>wyW+Vc*YP!3 ^ %"`ɒRcD@2ܵG5gL6}*Xl틵\"*p9B4MzA65L.2k,0^>G@@Hty Z4iepWtAh,8<{9ȽǷƶwZOYE< Z)t#/崐\F7ʔB>(&6ldit/=n>?&s]@Ν0Z.3Ĥ9MG6XIJHXa:C}3 6~>D3UO>[vZ_}סqN!ʃ -W S Ha)Y'lg8=`z(bwvi:2E!`;x,Y ߩ =Іj^ǻQ^_Yy`Q[&aYQ us0{&m胑*j)TC$ YQ>*P}H˥_7!n?Vا(sOGRBXbG/*󨴉bE("lrʔ$ΫdJwGp6 P/#j mtCR0}Bj̣RXvI>(j=:ECtV:O[h[5"uE3W. f[eܫ8P)e 0Rԁd.ُ:~}t<)/Q cOBGGp<"-G-b΢y3b#5RPCk{d˚ ح6d]LdLu鋶 LCzӮIYs;A@*nyڢKˏɩEWeMâx[*u -zҗrizH> 2$ =_j7{!h7Ύ|pfs%9LAQ,2WH(EEug&/ $̃cm$0^(K_ C]Di+/TRhOJ?Nޛ j; 쁳#ISm0Q4WՏ5_fd "0ԏ ~D}R'k GK1(_/TFȤ8>Q8m.mstÁ-`wZaxx";ͯ2o2:h*4X-hW3snP,ɞ "ޗ`7Nw8ɐD\ (,f鄝 IM|؟նkÿl5nv xL/LM}ݻ/Еum.umd>Nh&kԵ-h# +qs}v.L8c|P=/2,T,\fxP!:*}uLvyj{C [ ^܋lV͛CZk9~_+2_ʗ7%\~NVw|:$^fH-œl6[DniD>=}4b=U{xCu:6ݨ18=Z%ܓ&?i*V߻"z,K=,5keb PÒ}aM)dŐ".Aǝ2AnK% %7; QΤx9: J's9:(w̿sltWN~+lAڏm[w77n\W<9-N߹ti?";iw[;LvP2zrgkcl;#E*b8*<~h!:Q@qӼek/#@wꪫ' r*2_2mppm"Oގ:wFgRۜ{zh?U_3m3ؾ)[_./d jG̨.+{7g|6w6؟>d5;{O"-<+jaW22pWagy6&BhI2%1S*[ϤF۷%nwT QĶ!=00!dP$Oj!%l6bd[6,6`^Hfɖ3V ߶[8|\MQ lƜYxj?KO3ٲ%))JrGƼQ̼)2c"^–;@Y5u!'hVGTi M9#(ן<4s{@efQ`Gy 8L"KB3+fOx_c`= C@d-TOj+Jw]f1򉠦J -L[,Əvu&}z)AԫyzX߶"MWwP-蒺Mrk 44LZvɎiZcKU/Nja,a !"Y<]K-{S &,- l5V(DSJZU+6UԤ)jȀMXju5xkOxkCf>v;oĂu)O[H%rJrZNCQn?|x_B*kgYn3:B4WͤuQ.RMF2>8G3J<ZŠrVŗY~P9w;< +iչ+5DDhp,;ʹjfƼ=䵫9 3Ƒ,@('h:Ƌ&mTkPq8󨴱!ä.#Q{== 4V#mx _)IfC#yFN uQRPQyQ u:]g*OU֧c'PfՅԭںo>x,uP^"yXdci+Y_'z6~(+q$U;{S<^xGn}ouvXt%&3`.:gA'%O0j@Ew:мjdqge4c&ūY3]*tI* r6% &AR^3$p,a2GÇ}O>W476Ոn7[YNqOecu/=cm:&4Co<}iAO6ăNYm:̲f3J"MK:Ek:e-O7 6;kh}x?1/\g^y}7|4q'7o^ o.Uξ&d5v 3_P MpĹVjlU  a^vqǹ܈\?虽쪰:Oob2AL29zXvQ VUq^k%@$Ǡ#o}TscFW}$yF$y^2:l4/maԽ&oL3ѤNIq!#ĺ~N>0=ٞbDAw Oh CTѡ ֩FI.M#Œ3ze{EvceR] ecsERn`{ahZ]'3W0vIxV[mQ8f64Sc%WrF.aR6aLv0n=,L ZBU\]aJXL7e銛 ljQƀcHj\}MGޛ [X@"WdNS<+#(;<"w~omyL'DpEbY?~{{,o,RD(JbC>ܶ_dՇwffsܦk3ގ&~L =$&Cyd"le؄ tQRʉ@*΋7JՄpC#5-Vgo !Gi 4&NpOo޴խ9k'y=JS4/;٬vY3MiB< (Yuv<9_m@|zU _<';^;#b})Kywno%6,i7-+v(k6ic"Ym=t#WRTmR[nafʭklW޼(IdrUU5=^Dfj}-:$rp( %\x+>wW؄ Ou gq/,W:˺/Ɏ+ y+&Lo) @[@exbiu;:Ykw[50x:rsS&_Xxf[bT:7ak}Yx<5r'(>q-proɴ2HU&I-Kmhɠ\YFY`|fM0]63Bw5%#'iH(8[*k.Etc&aNmVJQKTMbX4?#4c왓Q,<v5?J [Js'ڛiӒӇC>䶵hMz__m27b2HC' j ,JN؋ LuqMZW7'./^L^DL%S n4:OW^of߷Rпlq{\PȖ叙y4*xBav kx@͗qY’.3HQF|:rƔ9`P_SRL 6b|jAn~<DN"u0Q\ Wuާfn6oH玤N N'S;)̓vGvejOXJUPsps<׷4}am}SjTYCheubm20~t'r3:_H7M笜YrN:1!-z\MaP}l&pq6*_UYIG~O_KU8FT{t( av"CBf_F;QnqӳB$MU*rg,^GD,IH:7FD Jlk6c']u;& FbFiB"&͙MykUP\M]J~qZ JP$5K?1/,# K:I)DoY:Mg!'S$M }ÊN~$Ū3wm6]r׊sO^ ll 6H{RvBoLg(iZhVd˂]w!r<3H/7CyYN9Y@LceY֖Y $rz2dk`8v1gI1"0k~,c$ tyh2 ^/sv骩m{ TUM~{WÏɿmkUٹ?΅s4a:ZDg;@Vם4`gلw]x/goLvw'vڟڔyK<+Ǟ~NF=ΐ7.'hٖ}t)vSK4Yԉs]kWN-ЯK`~kR-^"9BF%`%5S'$^\o;NKM#_5yr֖ jKgMdn7Y n NlݮmGYN̂09E&WKbK|ĸJﱵWr{ݷkQcZ\2R؛Oۡ_h]Ըy&܈V;~M/׭n߮>_[./m2A qJ{ >L M8Af]'vHTUOμŃ̚u\eAb~u:ynwݥIٸ$j[QV*b 聇nEC*ZɭEo?҃&k=t#=KTrfWQjJN^yٔQW/Oo^rrj;NM4I`0wϚ _ߜ !Iouz#3tzi kjmfL'k ^9uDћVnǼ^߲rn_CSC "6Gi1#W0=p']@8z}Q/ F"̒ &=lFwdF3v1FuDFYV'F`.bNu䡁 Vl|I׀ɷ*~)Z*!+uQvCM/vԂ.qcYs, wDiN6 YrLU߲[crcq5)V!c031;B0ތeG͝UaVNUe (;;|d;_TA"?/}Mi ;]wt7WY㰛nNgh7EB7_RE=SxV5P sm`ržYazRat k_F= dVٿgCj߇%T}[n.Z$Uq:ۛ*<ggnGh (U?.b=Ђ z3ek 4 v^QVJRT+N1Ey D;YC+dNA݇n$9MAyhpJ=^蹭%[ҫ{\r8L^Rڠg8ޥ~ad8U=gP'1.#l =ΑѬzR6np~[EfnG+y|:fE˻~E׶Mʟ]f}jE3qMOϚ{d?]uU?#/;s~򹃫ؚǀK-6B'闘̵Lgcg&=G' }S唩VCIsyRCM)rd7&UC͝w4Nsca7fl]tTwݵFè4ou֍2B>#o7(J~jE(EM-P3/rQQ@Wヌ(QUm)!sG7ꜜZ4 …U lڟpd:Cce's2E;u*'$]" c4} vzyDzɨn4bTF.b4R#P*~6tjtŋdۥy1 W!ןD}glْW_A4R/u|]P Ǯ~:t[94{-.ǀyA0 x6-NMvM$c50ghQ61BnW_us;BEg}\"\aQ=#ͧվv1ŊSY(R.i[9 JdQӜ< 0@BNya)j0Vh2쬄sOeP5>I~1!-A8agjNq^76e/쾇ݳRuԢZ&UEJlpYo<2"_:979f阎.! hI4 RkCjGBu +btQPu/ А1TZ5V:+zp8jy\ST!zru8Y۸$ՅFuFY Tj +[kj`GŦ+yl֦Y닍4R,+h")=U>yV˕!V]Z8G_ jW pH ֬Q6P8=wQ9]W809{z$5p+҃D%ꔒ-R`5CbJihEI@xQ@-Jhnא!7#םY ѣX2MnƔi&#ix2nB~#}2n)Ͱ.woB( Yk"5nG PTF;NQ@(奣$%l7Q?lRPfB!wҤJƝaîGٍJ vKgWOӬL_$ta[!i&M>JLBfR% ۣ6!o "$,J{l2"Qo#BQ'!"# H:. o <9*a$ <1ʔ/- ᪠(J&$ f^o ћ}6,+7 g2.;H\Ұf,-JǒEw\Bwjǎ>fM..klDj.Xv}mW\:5֔jKضV3BS$l&ijDYdIO~q!rW)\3 H.iT2R ˔D'i>-(*Qoc$`g#Aꆘ0ߨn7.>x;w,yc?Ơ36I61q ($,Njwܴtr(yh2l{s\p@ 5H?]JHʽgIhhh{ ef zUs|+DWxst -}"<;p> #?X;$}upȖow/&ν'dޒM-3g֛떤$yIEuR ;5ItБfb{g-:6ާ>k0ڹQs.A,1xBU\tBBA= )~3.{ҍPa~OBP:sQS=:Ufs1KɗM @PsygQ')_@\l`|N16fpp3,Y,wZ1~טOnoy'ǗlfCW?Ot=Kz (UQCdPn.<=y]Sd2KZu{d^&P^ qhEAakFQ7><~̈^=QbyAsX Gr9Aժ` ΕMʆ돱, ,)4KݑYZ?0Jd\;|h~ki?ev宰Kv2)i9Jcj~Uivo V޴ʍX~eCkˆƆKڰZn߹ZXkon퀭:h7ΤG+Ș}I]Sfn"u!`*ئ(E3 M N4jnRXMGs/MtbRS{i+-v aJu3Z/WS9ZK]>Ɵյ68N^~i>v$$&x;ό/nTu _pdR7#ƌ]Kqk^:J1)Ǥ5$2 ;ʗ$X[Z(ޜhJ7*%2E叙#zg{hLK,M#ǤOkdւ nnVZĦپ[ȷkV%ʂ:@S>Զ}S~.vm[kl&żVLsHuvM[2/z9ն.S<#y\6 nGfmȬ@xʃEӻeiwXDv [#:bL_hkm[-NٌEZ~emM%Y뛮%Zbth%:9}6xn.^%,uXF>.1^xoUQO7}\1B,53V̒ׄ'Ōzw67Oi6o_rUqp,1qOi#*n;6F(Ny'+ܣcTq333~xh4[ A=,Oc⋢rx{+=.zfGA=SMϒk߉kѥ1|ug\==j=$rR3, xٰU`B!"LQ Jc@({˯F/43ibM6A >A 0Z( zcdI Q&Z+8LTW& aQ<a"*FS)1^T}uМ5`-q'6nh־ ڻO׬%3<h%rܿe :b VY zlN]6p/oyiOc5xrM{>_ؾv5>9Xruʓ3r0rdet|¶Ld_*5hct,g}Wi\<csp=iv6l۽N8E߹ٿ}aq̈́s+Wߚ DٶD^؉>[DPjq\j3th d[)7rhUW]jiK97 X|/>g],pK4YW_ځ/&-.S0+0:AH4bc7o|~۶FyWub^yV{1 o8S8#(緥~w޹jҢ6ĉ"h0PT u) $`]+E:Eq؎W7jD-7(3uŲ{Ql`Y$OCoɊ= ;h>E3g^tPeNB*ʘ!x % ֙Y}IK %epH ZR ́H+!)ʵ * 1B1ˬB`> &)ç & ),~)|H}ؚ"odA[aO:)禓GwLr(yļCgQ#[UN84~c!yzݰҔZ3;zss.FMؾ1 FSI`A 4QByE軼a"OiPSbnByḰXKG`SVЍC/|WM߫ʪkjv! :|uQ(UϜe׷]N#h<;vU{}fjH%X&? Vu~V~j6A'MYvM!GP۹re紳 Dk/s)kq8vI8#x G,c?;_?!syٯ3ηw>w`||tuP~IhhnE/&jy+ٸuTS6ooOoh-Np8ޗU2$u]v$0$ c ߂ST6hBڭw.ci[ҙ-: g*Khq{FA lW?}'MR~<3.([v 'Tgx4JA]ԧ?21:yAc4Qd8`b4Dlu*l.]&' NY ?_EJOG#yn ^TA/UB {dȎU}xX1r_i}~8b*=^]W*s->KdfgQU(s,ZeM\]2)1 $l!?OnG'o~P]h꙾V'E6Fo/q+Zj z*S`OƁ| MUa{o03g}(骪5J8+5OOWU$# +Z J,2Yin>ŖXp 'E!4l񺻜i S(߁TR_ʠ̈́$^ŊMOwޯ,cӊф惞\I`T)&IX3W Sv$Fݸ{e1fHțaw(Q \9u\Ox7NЍ%hۑ\WTT۪˻UmʂjrS-kU-nE*+g]4u,}뮻mfmsMX9UuuUNGQ>+UUG7O(YA!9ې#I%y\gf6)+{?DC<Ukmb~c|T`ᾮ& >E7"B1;/ ʤA$vBfYtجG_))P@ p7:z3hfa2 :v(^&m胍ɛ7Mi(&+;vv&1S {\ر%W[7mnYm}5qoqQˊc^nBq]dZCG6\i9I/`b}ޥ75!parHٰ) |\n@s؇Ӂfs޿jZV+m#~xd Iq|Y;$`kG^i[يFTX *QlN+xDՑ -ML[J ϧ},i.F,2"BGщ0~IeOÖ[咛o}Ta>ľ/oz>E}ʋ `vz%5QlҥH++l6gSÔ|Bh8ڱt}C_Ꮐ֣*=d[™M{WJfw.a44D o*VVA8sP-Ҟ}A" @"Ȥt0+||E4N Łݓ1 9)*YѶQoP@ J2::b?2Hϴ3Y_nx[b¼Y1-Mҧi.#?\!Бck3Fʷ׌8'חed($lٷYS hC:Sli,ɯ䝂Fi$柌tn_=PpT ;(3V{ID{iEZLI sҢc"3[*8#^NG#c`4cCf4q&E:r@B$=DMRI'04 'yP^?RxS^3Ԡj"!psmhg8G41$G>LxNy8.'RԇG@"LC8S1I.uߣBG?> sj6خ0FƆ{17qDXSJRʳR%FL!sM(~l^0av$.XV]Υt:Jt1"GЏeC7aR.#*fE|[rX\pM[\c3`Z*؇qfPW3f!u61SJrmoXQN[1c_.ʁ 6a<K#QGRs7gc7P߀sޝtos02zr {V{n͕{6>]yTЊX(|'׵h%" ׫{i`./Md!]Ђ[x C9w<XcpKCabP#lmПur8/^W`Mfs (=TA{r \X݃f?8:4gdYc O*EuDmO[, fs 6W X6 b%֢Bۇߕ"l?YkZ&|l !\I8 |`&11P/ IK)){@'ZYhv&g @6` wE&yIIJ9DI=Ab̚|/HuD@& 찇NQz^~y @^,, Q `qq__X(.l{^//T8 c#*bi&OaS l"y$&̲Ds7Pu =j\.Qܑ?҆|rz4ʻ}ǃ ufůsfBQBEv^M94$?8<"<.L3jL(L5FVw߽wpf.p©Mnc^8(Uν>n.Key@{SF׆{`| 737KݒpȕHdQ"p(@dY T cTYK KJ+VOwdC$ZѧtHοn w? &iG, 蛙|шD>yA-@K#Lҗ|sĩi@3@gM/<X6t\_ey̺q*+j/2+&Z=9s{] FlƎp7@Ŭ7G/Ð"^9M4%?}e %Ci*fFii&8{L?pG[mXګ`dl'k&cb5ncd`A0g -X RY< zŽU-̞w' v8 jBXV>גk5`Y TTj,OƧ. fء6;*;ZdNywM" 0ԈKՒ4D=#eLpEH6_-8(uwʫ%S$#0zޓd%NQoc[:@~ƹOqS>P 䬕}Ǐ{"f+wm3;a8Zx 9a>n  f|}XϓѸ?Gc"[yggYQ@z䛒K="aU5v:t op I+' /NAO٠#HzK/ ]^z 1Q80)]h" +_TaU8icm<ǥe}d@ųAc`h9NQS&ݫMXKX~ JЃ͠X)=PԯuM7:u&eVb{u+9denWjdSX 6>A8ozt+$5Fv_iN&,>V2 7>#_f 0ZҬ`>&$+H кeH!oڇևhN+?]¿0Ck~\,?0evgφ cuH`s$%C_V@DbQRUͫYA$|E{Z|uaޡU_CSnn "k ǥESʇ8A 2}桫j >M_dd2/?(Jt5XOwNn r>-|<+> z?=y W~><W䯀\0gj[yc~޷CՀCC<9OE2VnK+gj2*j~y\'oޱL+0+1{iuW7*voܨUjFc=|LƦ~߮e˴P9i̫ˉ~d 9yr }uf**?8?'a"U[/͑zyU@ʙpy=K.۳H+9ې3۽RNgQ l]}g+Dd3E d٠C|="猖D$1K/%cio&5OpFrrre+9Sn*YLID##@ fq 패a#'b}=I\̮' Zh|,=:=(T")F`EEVj,Q|FQ_/a| 2rKbIxX^bI&$Jt2(i]NEWؗ,ޥxVcmpF&+a) z؇d=>>1F_9=!~S`;{L|cpn|U^;-.߄m";aX(Ȑ1|YYz_-^U{3u!C+Hn9d>)Ȯ˵UIͧ@E$*}*~ V9_XAW6Я5DT@BlEM+Քd0X v mRfFu%Tc^*-q)tS9岠G)AojYJ}A8I}JJer(Z`Y~IrXimf)~U(0$(@z)p_\zvOw^9;]WU 5c(? z?ܶg'hNrG]ua!z"!`4yp A72E{\ G9 T2 ftBIQ WsxnRP> #G\(:4QSR 7~F9r@ :bQ&eP3RNZD%&J ~2{@1HrX/SV18cYϷw5m4y /T4"9 |O"u(M(֍nb.e1"r% ӆڠgt }*ݶ7DHBlg]rt9m72Z.T 6kuuN^=ŒBaF_lcY@2n6J Ea (z6id0[\IoھfЅ<jW}qG9aM\WWr!(^k=sF-멜 jH NQkpè],/?nMb=Zdy׻pQ/{B5T)~ +0cы[pkM[J%~uD.7Jwuw:l{ٻp=[amEeĉuB=\,UX簙ŀb\CӴq 倢#ҤZa͍ta[;OgxlLl {]WlwGO܏z a5xsbV3wgug=N~%8wo%q1c>(G3J&iJtX2E4} {ѯDVV"oN`4~[b1BM%CvL|"0-m}Fq$Y";(:jш-P=4]W im+wԀvZ9Zی|d涋]v8Uzxc]NnSz묝-'<ShC5j<Ҕ {=.t(F~>WZYfu3 i7QKT h2 SF}R&U*0, 61*ap2Հ::A/J\``AI _/qZΤoޒWz]aГ2KV@o/,hZ[8FCwЗ<O~pz7Q3;{aN jiZC1jvWqӰ^@ub w+#!δƮ2_Y~t$ّI)s";gZA IeߔZ=FaV;vkuvfe[ϳ}{XOV `^B5 5յvvNNyJ>)M`h3ͮsw׈sR7mKWlXu8wNYok׬?޲<;Y(6.x&U8ǹՓ9G̯/!?C#Fl ndB]]yu? y;x m/1HB D_A//Q!;tB!Ll 1q]ee%]/+ 8{k:|KVUY3i$ambAl]Vjoinݮr.xIA->9XhJf3UVa1s8ٗ7RmDC1/Th&Dc5[O`LoFE &_ugKy%:jz%!W`׌Ot\hԆMKMgZ" H{<ܲh䂥3BNOsimM6W˂͢oab x+@] &m 6bZؑʩ;G_^W"Z-FE/.[XGe#^eY3,1h@$NE `u:i4jAy : ~% |8@0mLtJ<,a ZZQ x7YfK'_6=iV;h vo8?i;ZWdu.;9 _H@X~w+*&V݄0ƳG3y&|fsGjlO8vN_Z?dy1BK:87+UZf{R[$Ґ&w(T5!=.MdnEk2M =2Mt,uEFq7-_ h᢯!ZESQ=w"6xoגyyQ;aZ@dԋc?ڭ% <%]C^%=Dhtw 2}Og+a9g5ԸA~ij]iXcǴXmŕc- kU¢HQ .aQiӍ.nz ~LC}SPaa#Tf-V5K-=?QUqxl#_X ,U{/~|ijQ?iځuo'?<]~dlp@`KysMI8pj 22 A8_;ͪKpAu|Q__nNg)!(NiU~[^T VmCg-V祯̌$eEz h΁v@bap([Ӣ~^՘)8oy#km>-<n~"5 > `,g0}`O1k(O1FN/2+lESs_*3 - D[H |$>h^zN R % xN!+ސ_SRCAp4Xetf+XO\7뮋/FähZ, :oEJRb[ hX`l @6)?llGz0=,El#;BcY[7?6s>9=1, ?䟃"zs`<h\Ȥ?,/gyLIhkh6ҋ;^ ׮}|GioH'anCҧvѻKNuu9/ mBrhSڱtb9y97e4O1 ĺb.yp vY&k[j_8ӟ籺\$%i2NC;q*O<$~J>oIzwm"8#e"L :R4pE\t#)_/9^\-}\_r9*GBpH~}>jƊOf/aAl}ع03wWrKDoSB﹄E;N#iQ"H܅ :33#^bZ=.* t7 /lN3/]#ԊYod/2'a-ra|ƙpg+}C2ٌ,KKK<]`mfkẔ&ˆ-NZhn;]-_TDךNjڢnNO]eOȽP4]}iCS]I_%VuY[ 4doD:9a*XP} 3FU. !nS`9^ik3XWG sJAyx4͢}}4WNIk{+B6c[z=kKLw|c\k)[#^ '?'xP:̚wkyݺ^tZ&gX^Z<4\kr|UrH`4͇ >pklw*iBU ~u㪗K:_m-\bl@jGC1`Y*IbQԟ X=G,=i[:[Y3 fȏgY\.۸EC铞|; FS[Z|QЁ> Y`-tSkESI]Sq ` k:/mդ7);psk~&*.(O^ްoPTQ1j}l~e6w댂NèZU@NfIbb0SB4TVq5H `9;Xed$i8p3!3@7f%St3w(7f*ojB(%&4H x*LTB<qJ7;xĒB1u9hԏ0P7@š!Ov)c?pY"h#^ކV!ю@JI+h Xjȏ3n AVpZC/LU:4qaEaa. `M18@ a)p#`DIqhފո>IP!`6N$Or[FY-aMz-JRƤsjh642@ =?4 yioO.6&@ƪ8 g/"*,vh_.@ku-X+v& N8,s{YkUCӂv#tᬘVf(:fi 46/9-ehtGS&T#h*zDlB J@]BZGzղ2Q\g9Fc6i, 2FV;䝎+ ( S@VL)ݛ%NV :aE(B?M'8iѪp|GA5A{z```]wxBaU&$nunw/E!ltg6tF^`r ΀vMs²=j_/ʷNS\ֶBrgUX49m_C{3 SjҚ=&@ h(6UCZEJ`pj&=`ZJBsŌ aL fɤee2[4_6{A\qڊ % k ^qTUJjZlpUHݖymĠWOY\jY`B xqz0 `4 ?1FQKnEF6Ȏz2zKg,zBy|Dk`t鳲T9 vCh hnBӺi~l/tkck6x֮r(rXc7L)D ElP{W(@*M1G3Q_\UܶeIsP( p[Ym\zipG>6o|vݫȃxHwxIJQ$*c|ZBSʳr_ tB[Q́F&FDǦ ݵ>FF^n4ĻHdZg03LE-6tmYQy[n[uZ]k]O-\JXwP4Qg8vi"3bN ~S QK.B.S(Wb d'~LYR4@lm$/kmȕX_51 isQ u Pf`>yIt/&NK4GK at=K2A≫ l6QK'? ݛR:!+ t³BGw$Iz508;6 ob-b!B6 uٳϢ) )egKY@\͍4VB}f$9zx+C#…{ i<AǜJ=żTgյ4kB(gjt7Lp:d<ÈSo^,齺S v5ku&sQ9QcsFlǜ- EЈ`s5DrYuo{wigamj `Ihf܄vSWzM?6YNB&Cm @SY:hk]һ 0b_c␾_]|Ik:dMZ#kv:##^55ZO]ƬNgcD#5XJxb[ZBPCcHTT 9FXe*:~gbmQ(-D6n]]}o #˧QA?W&Md8qWаcۼIS@.js1 /1 Ņ9l\>$6eb/_SfŲ'{n,8>;lO00-q`@6m5 zԡwգ2ӝX㬞VKuycRT9|b $OmkǤ%̣bgDܣ/</_ʷ_}~PDx5(߿|omC٫gߤ俾 F~VYCN$mk/4U9'(h, 6qpiĢU,i8hxk#9dwz-]|VٲY>rI@ڒ\0׷˷D]}JNJ9 W.h,cи H%,g5Px j̭fvU\hH[m\h5՘;;9i6_Q}֢c&;ڢ19-}>WAb .c)In%UD>,/h021:AJ1{+[{q`)~jocGj1iL b*idS!2}5ca2Zldiˊ9KqsTɴ; ;afTU>%+kbGYjQ,VCj)[eP G<\x՞[]jt=~'} 6*#A8ϭT 2 XbKpDZ(׷e!?x2K-_ȥ 5‚Ap ~Uj,{??Z/go~ڒ[ "m'N:La: hx>,jQ 8;Ѡ;_+BU۴}KPkj6uO{{iI= ?s~^X@,h**#Q԰Q3aXHp)Brk$,1J=$_ߥ9$t0us0(LL>(U3')˲X|bk{.$#{b*M 3R*V.+r?Q~{3FO]j\x _b}*JpPh=->"WT>#БZ: a^a"/9$3yɘHy❕;/)aPp-YVtEzk;KKCm?9 iN_u"iS"bPɦ˿ w:W(x 7(cغDdb Q"!24: nH%Ux;R<4~:wCr\32;^q]9;ʉ4q6{;-g*{tGwGUe{{7f'3Nzhw ahb(Qv,(YZPς sLt??0}s9eqr>rtA/;wS@ʇ*]Nr J=RҵԞguH(-]RR$l^ }{n"<̩'T] Gh=:6'cğ0J1HC1TOk0q)}F?H}wÊہ 4i؟qOm'ێj%#=k3:)%ї¾袺sql&{dܑxMJfW8O  %ET O'%_IhN$tϚ"58>sdO2~$3џ~烌VJLLLdRJjˡ\䰼N1=f21]8GЋARyã[f jSGZ3GZ ] &D g`6Ko$XL ZU}xRy$fsw,J6ؐR( K |FKdUX:4ri8Je~YhO!y΢R>zVtUGVw<0v&7TG8VlƢ!;^8OW/&H#LD90((ѓ? a)Am!L<| ئ%\ÌL4⏕`n?`VWkhb+iŚb%8ti5@/th$pK套sGXh%bɻb/u5K:`Ěcbֈ^:Mžrݹ׶gY5e\pA:K#xs"Nt;f dBC 3vDk/U1ղ9GsX-BC<27ǽ M.EguL͋\yY6{ZbuyE5%.wAP3}Snc ez52QYͫx`բ*'/ΗCi~E'`ciE*&9ҞKA# \:+/c)q!r^={pn7\ݱdq;zkڗ,\Ր9N.N[EZ4w^/<4z29愘+GU=0R= 9#}^)trgrt:".^Q~;3ʪrmNEE@~}Pf\tzMբI`/81iSNMPVv<_aO6)hNv9dyXOJA1`SNF 0d 7`z$ 8g0: aї Z\f0< \oqg~1?8`|l"[nb1 MysB'F~ZbvGN u_f͉kE/˚>6D٘HN T1P>GO6g\=WNeqot#uz:JO')%A]4QWCMR& $%j¢ 7Hl%GmPPF @9sBM\+,u`4cNZ#,U̥.aLQ<4I&ũ1@aWN]P9h^^=T0}\$y'ѾY!aED*nĈ\nE*eS4OpD1Kr2B}qj1Ʀ/T 78KYY&駵lWSJ9=4OG:ٝf+\*Z8Nʢ g^@$|%-ϦWHM VLR:/QJh{8s*dXJ5`j[pk&UYbd`l&LSTr@ tڞ){iEڲZw:0Th &!̀\V`);^L1C|]ߢr.-8eu J|W>RNr 8xA#b+<SfLM6e- !d#_ԚQ&qqPBkA(#ZqƗ!Jpl"1ײkIZVp@?-=6Ss,e:3eZ5R9+7N9InۇםXgCSٮ嫳lmu ,3m9zOPEǰB^rF&B^mc r4sͅj\g1H9T1rFBCZ0JPhwan]bյP5ނGnWgkuʥC?■ͮ |@-^%;x>@5eyAU954mƄWbp\!,GhD" 3! 鄛HT\6H8`9LE5tV\){`{ ꔻ@`N{9瞞ݷv5ٛ:WnYu?={%14*v e\{z?gme&b+hP9B{ OQ,mճU[`l\5zHṽu=`zrX ~UӚ gv^5y#Q(2'}CWKs륊O67Րo6kCD&PS㯳XNoQ5\8<On}թNh f ft+x2mS48vו2 )ѻ$:(Z1FbpB2kYcÐQ+Ꮏn#4wݩ/+kOT=#ʶN= ;33Q @&.֯ɗ/oD{ L=aMM=I;eχ,'d(E5^BK1gՀbAtp7oC/Ҳj8QQޢ>”YnPj.$Qlw[Dž@>|rFR=v?$ksH Lk꿿 N \|D gC ]ݭ~wS$cwT<б|"QDRMcjId*Y N5~wQHպAk3`$0 t1B(_%ZUh*\TzR׋PyRя9h`AsdӬb ဟRX| NjhZ; 'h0{*AZ+ehȦ`<r^PHm˄V}TWkO' #gmkOW.QZQ {p=4A6 Ҙ‹B3?#9Db%>OCxu'@<>W8-{j>9أW9.Yz&omC}s1e5\Z|犩] C-`.* 45K}_.]|[NIwzd 6?rp%K끼5kqAgZ 3g!BE RǕ>Cl)I]{km;sZ=-Cs[֯{l|~󪧭[OVƀ#@Ik<I{wKk[V?ZE?oxtϥA E?PR> Tk lR"7(/CmUe@$8} , a[ҳxq^Q:ZRPjVu t%n2f9ر]7~,Un6c6:gѫ +-.?M&fv߱s#zVwq:꙱m۫۷c$_g)O&&\@bd34n'BX̡1R;q"LN,`/mO䔰m8F0V\6&yhM&t3J0`g@5zzX—#Ն1oԠRڮT}V*yp-"D$ן2pԓ1 8G07Oy#xh(> MswLiw:&mH)yi*F)I$qKwN^~2I6JU`>u0P5mh9vyռ%M|Vεz0cQ[} Уcvg-3 盲^Y)Vؿ娢VԳVBa\Α.ї-&<_60¡0z̈B@} 0gI=FS]+(]` \x\J KRqbN38ʔʗ5 f jA3]֚@ZOjM$%RN Y[wzterZlJYV9q* N&[5L[2<2?Kl*}*g?je܏Id?r `^1}/U߃wyE|k4~ NT~WrZ@ څ _(Z VT%ZZ#X>u㲻^Eo2˽T'v <Ր*`cN-FK+P WAv4?JScF'c73 SRӀ\Q>j2;ⱳIܯ3s:,([.edW=s ~=; !FKl*`DǯP 1I𿐁I Ș,a8pc3X)WW`:5KQy7j$uE|pM5* `lh $J6R/#4*8BݺؖWX.m)R3fa-v4+JP%Fvځ'C78-6F @6aY9_,GoЧͳ%{#QkA6>ohͻ㥌d͟_G蓌/tk `RӍ) |:2r ⯿s<ʖ5E躉]]Zm/xƜO XR\roytX Q]$^Ӎiܠ*nR gf5/C7A5 (1Gu@|,J$4 DIIDmx8=9="zcq2wНvȅ GZ55!_u*ZmߴN3^#7$QLZu%!^A I1)91C|GDM߰A7Y݌:֨n;VBNRSq%yo|&5زgt1cL0o1Cٍe^w>½!6jf4K Gzi dߴL]/y rEF~ӛUQ@߉`1qUwb\L(bY%) ZRlҿ˪0-WiUФIS+_!y]+r=`'tv7{}1{\ǃ$ cϜZ; ;usg,kv۸U߻|ozrPQwGb "]lɵ\{h7{‡{8ֻo=`#vN_2}N$sSz̙Z 6t6@fn:6i!T$" W8=(}mZx}}5hKż{8P޾7yƾ7^:8,B7l{8O<Ĥlt jC`)7a9Jl6C/?4gZ+q+IaɅq&g w.yEZEW~q7 K&*/: ;,woܳeCk57nug͵&շ7ڱf}?uP;o>r;N}ztPu]C<֘јsUۧ. o bo?7gW ,I$Z* !N|˲f<s&|헪m:?^KgzQtc+kx>7n鸧H1L"bN65|#.hd `/0뉚]R>[K R;tHdNkVrh*<;?Gj3 d4 ьi1;^Cg&cP SV9y8xqcn蒳ѡϷ]j^ 閪8w<:ml튵ݳGVt*魏7Ϛq0Jg!=B_Sb>7LS*J&o#'q&]+F.O s!qLCDktK||4cLzbU[)3K!wY޶oXq¾é [?b(\5La乖/{s atq/RˀƓ/=V!疕 rR|BDPxt|߳eg)VA"#^A qF$ڻ"db&B%+ձa6U{nm0YoM}4Ғ|y|*I{6b =} 6d1yݰ=s/}qU|gFOS1 j~;q/^u 5eZXnKDkc`LSUxM֔v)#(&:!P UԤ:ˮ>eKqGe6(ABO3cC~QgTh&*F&ak[:V#UJ5.Ugp+* ¢*f=c(ךW1^4٠.QK wƐetC<(a,zB0V<[M>CwUc:y'܃i9}^< C08C\OPE^1sZR5Hvn}}n6mpb1, P ؊ A1eWv5wǽ# h#/_]ps3:u8ifٟ>0[v۶DY4ag "DR 9KvHR]SPŷzJƛ3в ?X§)VF 1Io0O%eœhyw xA;2ބI>gvz _ap^i5ҕp}ϛwJ9ˉlԔV4W5qH >.{C[|_B>N=^[r9^5bUΙvJڂk|߰8NgNJhJ,JA9*rDx0s{P6_WFjpm8Ϛl#)ku?!ḰГ V{=ӓi3a3 `F`vin`n7< 2n7unhC"$T/^BdG#yYl޼rU 5) 嘭C/YZ,[, rͱZhXqE~Djŗ=kqW[Y$9.v1rqj3܈m7%q\br2:.G!D8<%rըרi^`:X+r:]<cr6 yi䜂?DE;x6@KIhu϶aںqV-6 uU;V3 VZG>E ;B41zb_h {b#g¼ p9t(J8!RY'%saX{D_!"8dr50.&ʷӾ6ې9p:X qw3Ϡhu8eD07D{ s&Byfth sȤ'7VT lL./!”.75^FV= .H*^WR֮,_0.iW]ee+ܸ&wo]MP{(aW80=p\qZkք΁w3V]"Kf EJne*kT7*>q{-ȕ*LnwWXr. ҫ.z=b69bX`-Q @w?qmEp_|#KWW%eB3µ{ҷe(K@ږ ˃K{[@ Ǹys0df Q9)8{!p笯k.U }>}kk׳v@՗.q٥W&oE3C^?C?G[۷={b<}aA uip(uiW2JM_+X  ^]"~ǡ@)<MN=BóM-L!mL!]}c@ж\%:%Ko`**|3*]I˰@uXK {(|I|~_ hq% A_&A%D̠ڍޠ-hCxB>Y3=8:Y7bzS8?%,S/ҋ^$(3HݝH $#BL*f@pO UFٳ \@ݟ e EHquAo=SgDQ.b&.{f׋w Z%0.7s??~u?sȊ 'D;FFEl188:UgFͯ_6m 0cYV7wU֜'706L6rh+FZ|T~8155ipMVOKZ۲s6žbD K읁;!f I5k%fpoZNK$p܉7&x8"~}3c@qL4GK2m L5 TNy#4I <1BD,5X ay$yRcTPYLєP ZWfjzA3*SUs(go.KZ!Jڊ&A 0%Έ-B:)NゝKg u\6߸~-o_wSg+ggC.f$]Hx Ghc n@dV`2]zuܸVJ hsUW+w,WD}nOӤ тf}́Rj5NͧyO8<lH.6N;@{ È^x]8!Dh"=eN 23x,> I$,>扵pB]41+RKH)'!G,~%!z}< A &d!t2B &Jd41Q4yAI@6d=c2/c~{V̢4 WwvÑ@|']_41zJqKOtT)j$4+ӎ0KQ1sm|~2k5oZDnHg 1,:/X9c^k4yUzKqjNo6yu4vg(tN')&]tjJC!SF4!H!C3Ą'$O={bj6iA9CN@qz|jP8uMn˦{n2z$aF/K17~;D1cA2=|ɪx\T>m:Vb̗o}Yn[7}_Yj/c 7N\vu؆-5\ƭI~ĩ /,H]>|xq"vJϠ | .(D߼*+੧R\N?hp;$OUUӁzY&7uj^c`+)4U3ұsX&:tq{,8qd>IML]Z EM1V C9eV H꙾rJ XEE ֣o_rUxv|0'5#GTO|x\.PިDK8ćGKgd,Xo3.A 5 $@k37_ c%ByN;IpMhZUTM6 ;$==<RIR5cX6IQ!3;*j n^JCCYzAHElEz@.Y!ᩡlI%Y@Գ2+^D*ԿV" h2-0e򽻴2.tKUr]Uт@@]bҿk5ԥ-:TB nz҈܄ n"(E.VX䫋\I^X+PM2q2$ E)2(O\"DO}Q :Z B"g[?kDQ3[]Ь,eR*7j w킗ƤwFFP^A}AA=pQdrעļڲ33)wgys&p߷W7z0 D{satD ]3jA%S VW-80{WtNBD[|D`- BU0?1DɠXTFvKR8|dO2iMA9 6ز4OIwI~y~4=:"`h0* 64` F)br#!f"G#jS1s2_F8tr}]Fsu9bW&Se!n%~g!a?FD[&NתM8! !P+:lbmVֶ̯sYD󂼊%tH@`u* za-N2T_⾗+ZR>Y-{=MA<ɭ;S;xށ>\23 ['4'͝y6dF[Ha,rTH*OQW/JUZ<֋puBL!LHQXPu%!]Dkաm[")\0$R.w`бsZ"ebEVŸ]ӭ(8&t{+s^7{lyENK5c5*.J`sZϙmW'|/w;.Ѯx`mi3._#,9bnVw~6(b#0֟dD0Tپ0)H -^L*KlD?t0̹Ep|e ,uO =kv g8b#+6B'G|bLzpӓ ʜ%?ϔO31d~rQ|ϻ~!*LGZ<C-%< 2ɴxXnW<{;dmKQU&!h9W!sDߣ7#w_@ '|Ļ_oPF>K *5D"ђb2x8@ Yx ">!~S&JZ4O>ˑ!ټ;֗ eMkd#+MO#@ *)T=/9NW 1ńA)_$7">sZ̔JSrmXē`;o]5'\G] O3`TD.ķҕ'130#nCXoa.& aH% & )!i-{`D6 P fӌxI;RRw%cÆŒN^^n[^Y օ+p [0-XE=J0#,!1@Q8T #~!?؄~<!vCq_&`f} 󆂭t~5d&{ZpNMWd]iV\WBQFID$#N$5L ]qPXTMjVDIh>d]2tx9>>]rհ"0|fڜ ; ۬n-{w*EXP*sǎpj9V8jhJG;H[K·%';VW9hJ wTOoϢ1Ҿvire/g}}?\cS[ڲڧѭ5^sZ18x3N]3L5i'O݅$ #럍 8\|Տ,t' z"`Հ4,{K};?}͍^ge5r[<4LLuB Н/8ԭkGV$ʗ͒<pX֢c \?SP{zmZhH Zx*RkjJZ;oR%UYOVV*__?M̺vvqRc =80jY3}B-Ӎa{- VTD8h{} e9$![N;#gV[eɲ$WȒle٘blf馛N$@BO@R)0KB A84\KliJl}̛7oDNOŦt^'`HT.MҀF -' =I$ݨPWشY0V3V"ར4h=sF1\U l?|U'EX^*ՓbhV |(S16mZy|^v'`K€ ,,,/_>_G_?)egΌ1(; xϯMϯ}Bh* !(0zOެGvJJ<{cy K1qA| ^t@K9 #72e|:?\}c` G0%S вO?\0=C}%76 OuL:{gp1`]LKXcr,w'cAL /?d${mX3x9OC&~ϜbϞ/N W {C{m߾7[5ƼsO?ӧ,\x]!.gRښY:*doarrs3[{VEy>v[ˡoXM@Z! +VxV4Fxanwud<,>8d7[1j:pBZ~f3B5S~VrnV n# ~0,/x聞?^ԙ3e /]wuow$3gbj4ר7!*FyjgQ;9 ?2~~hўtO:)t='݃==CuY4$[:, tBoEԘLoHMe@-5 ,Bo;{q^̍,f4&vphȻv)"< '*|0Nز0[JnEE.W :LD.D8ߵ?ODPI1Wes烏8bavzigk6~[~΍qD >MfU^OM8Ru6.x~jTAkMgzև:j崉aU3iPRtLUxY`(@|R* EDzgcg@ 'uA`2+,vЋć/ DtUwmKbI"et'&d{bDrRINf$U`>[2ThӌNՅk-z*FO<(:sXv7b2uTt\k.7ǻt(?GC߱7N95Ct%igC̉gS`/@χU0>`;lc(|0v0:Җi#!5a *:0,O <R|MYJ)llj*SnE뇀`ODokͨCb +z%089fx1ÆiaPp_?=/!Uz2,lOZt9@`~mnCNNPf.l/IMlLX\ܗKj)Eu%u*bN c 7kg1(  ;p{1-g1@\2t 7D P4-oo')%z29L5)2<:B&):O¤T]EݶK~M [uN9\[F_)6TVpHtKu4ӬV6_WʧU;(+4%ɤfei^oH$S;C!; 竭>N5)D{ʎ! K} rљyVЌw1Hde;N \DFChWvπw;ty9rӹp \;>#~`)ahZbizYjq;~\lЛS +rjBkoPl )^NA]'ޮh}f"c.!ok岭o<PB{?L'Eԗ D =]*.gJŶ}Bot&& e\E^׭{/NK޽DX9#^4xC_ jK"wCjM{.(,ր+MsQD QcTP^/4y5@^+/'w4} Zsũ"`W% yGIpC0:E?kݺYɎ+ U"5U@SxW.0pKaX}:]zInN6C̦߾uQ'|䘔UVєN=?v7 9l&mONb{#pG^]/ SJVN\*T-@vfVO!h4RhtLaH\d,Ӏ"F'aKDPo(z p=cwd7b]Z8p`"2X:"ŋ׃'H-2s֯{/Ǿh{ThrĐ!CT0b/b  Ԝ[9>(^0atvav؀ńQ1So4Vx E Nln=˜zxϒŒ;ؼѤ$. )_$1(}5$ӊEP۔&~F̩8ޫ`(1E(ѻ&G"T¹|b,i((18W0w#BSGXK{_gS.ф6g?{i֛뷛⥶v=vlTRa dځӖȔ \v힁UU7V͋ *5}$2uC0w҇AåήCvELSY>{4&~MjF %ۇt_O\',}%l)h z%ۺZyIF]݂Շ_'7~U) <2N(;h-Pq]aV%?yyNM َy[{[h1r#}B+:>̮ׅ N " ܖ7Aq0t#I$O*}~TwDE 7^ ٝ#D(%M*6X>$@p^ ") zAG% b>>T^}; OǘQ;c-/ ^#7wVt s&G'*-#צQ%^M'pc"-W+*m9zLԎp힒{ɑ]}}(b0};ax]t[)Q@]gД vÉ7g㮆'fToJfȬ"Rۚ˫DŽ* S?u=95jU!9F9j.4p|P{wΔ"Nz(mW`yخ`ŰKf?~Fm(ȑX0sr6D#P2 ='HBL"-0j0dNG̏rF=/tu?"Ju*/^]2Q.Uԩ\|OYw/^p9ߡ%Ԟv%( -FʋkBeNk=vuP37g, }QįK L Z>:MN⏆/"[I}II}{R…wu R_KnxRFmX`HS]}Gŝ-g(KqAM"qpn 8o|5Rg1:?M N 1a%O0<;,A[w* X '!(=i}&?#^$ ^2)m4sDE|gPb2Dq>n.*?W̸x(Ļ8sDSD<\"53PsA907@R Fq 1xodYХ&]bnʁdbzya(rj~}@8  >>4J.]RRŨ 2*F A6r]eH}KK۔JҡObƆL G hN'%+Sx̒jU,V/}2D5NwY8G,JeAh*c幔‚wޡ.0{DxSfѢ2w$F-:WY\D,oIyךnNI ,i)m#YǪjU-3Y$v%%3ZpV򒲗.#cNf.5 d$C},KSצIX$fX͊DM^uVJ0Rs0=t@kToRZ$bX*eVEWϕ5T0Tnkޑ 7&$2iyThF7ubqey#lR*[)IMk\a#u[N^3VqאnL(v\fTGQI7p=3?קw(snYISMg''gaFmL*1JJ2U,O}}]&k9-Di-%}jS*0XXWb%cRLR)$MNK,NcإUdfI$DĢ*$R fLMMuLձK7)lJehZ%V1՛ ڒS.u4elJ=RSj>rlڮb4%ǎ-Y ]#,EJ ؈]?Sgz-K=:b+ 4A|hFCR("F'ch)= EjjR7﫧W*JoJL2lXBa ar:ZcůM?'-V xn ]mPQY 5eS0 Ư_?^:w.rMP ToܞL"ʛ_b^GS7eZUd lX>ͧAGM1 0Bǖc(B0lEguK Ppl G»vh[!A9v qo9b\#}v@04> B4ZQ)?ݘ:>uX vn(zHE~Jńs(7Pz Xx@?n;E)҃4EJACuJyc>,FuUiZ: ^{P?cYոOBk3Xt5PTErׁn*~)pD M0;bMA폨p[인ւ 4]Lvky4a.YB\UE/5lbK2#M%PJvWθnpk'`@ɴ`iʌPW8Ġl%t %ʌSQ~Vpj*$w^#G1i6}"vw"bzrMZښ]].?+;z##Jz~:vvۻ$31~eݹ+tJG;I mWyؤqk*dƜ^VX_<:7''wtq}aYa#TH 3:#CyVZWjU֕?;AY|.d7R]&ODh<*z@ i݉AwNA%L @vI0c*T.39R[VJЩ,՜bM1WR ߫>EƉN, `õ>U8z/{23Yh확b^āpQ{/RX_߲d8Ȭ6e;зk }B rfq HˠfŬD ζ%,Ĭm ?sx\j\WWUqCS~mlY3M>qs3`ػoSL4.\剶jlu[I77쵥S4m323ȧꑳlg@ ͢؏1W%`T;ω ExCt#8*g30Gx{!w>滢xi$plɣ` ;f7kAfyh3>>GU4VO-HM֌oK<')m? %{[2p;>κK>e}}ڸ0D2`TIHnP(A!6Ƣ2hk}U3Yެșt#d}s|'s|\P_ξGփ$į8;BhQ",Ƙ{ 5k'ZUָߚ8~)A^R--.fGWԋZGE*.FzӘP.$-J}&\VTTnv?a/'n-{4yʐ`ʡ5e9<4eU斕dT U6?AX&튨Řf5?MA6eb$d`t%Qp3`sb3NnMSpU5G [6CnqҀ 0y"U(tK\SR*1S$AW~gSvtQR[ %ZԛgXo3c(|:c(sVl` nHz*_~uzP5X"ݫ~P]#jDy%Kj $-v!F~32ܪQ5`.|ap>nw/y#?X##Jw5( Nx4슩qV^=~R'Ҫe,ҧXM}jJ-)T:אw3rT'x}scFy7k V0\SM(2@u:-YzǮS8W[4;0qƷr6SBIXqLt&t&#M G#&t ڠ470݆IpX2M LuwDo2` %\7߳g ^mlmW)sX7ao`BfbnQ1J)?FT7ѣ;C6XV}EBq:ٗzhW*S/'W I~F,앀 Ud A:ɫ+z:b4'Ŵ؉szkܮ.08q/8kYHE>QvŋgO~aժ bx.쨽'TY&7(w^;[Ս$\0w/6p'">@'w.XHZɋ(jXyc\X{'Dy>z-zxy>xm˔ۜS^O]Ђ{E&``w)+ySL>cua=$+h)V,7RH֯a=U<35@fF9Ni@6݅LDQs-cr졂 z W^׏~чS25$Z}݊#q~d{VF^ުԚYl&'Jk~O V{W|šG&$d]8/vDj&7xҤU떦ʐ3 {W(1O-T}2k@NH:e i|},Nj$}^\X,_+Vr{-sv7d/zkuxC499/%Vϕ4] j3=/#TQcϱͫHBw _Ee^f[џ3 76N3w\"R1v/}}"O{?1 E>9|.mV 40 lK҇k|2A?g`f.}WF\[XQ:J1D~NN*(|C^&@Gj1:;kN\ 0ƅfӨp?$0oGG߽0Cは/zF4X~dIE[.9љwI` 샧'ab$~+/m`.- Qb'͛"+6XJ̓n+fA0H+l_sʴ!-TdؿOdɜiLjNqJɘeO;;%G'o;"),=K ][ g|Mo<< 4/c遷xj~ܱja>txkla^3qniiЗ1MɎH͌و KQj1$ag2g#K|!yeDQLxX{i4{{VNl Ѩr|_IG$iu,N?TW߂bt*xAutAՏ7Ѐ \84dه&I~Xsul0eZ~rsUJkG )2S~mVyn#~chVA+c%YY Z!W1tA1y51+AE8ICo.V3[ '1;Sv2Q:pؽ{/fb/vܽ1l^:fy%6?a2Gy8rmngô0.ׂ~XnjcpD1N70%p{ UWܥ҄oS(آ  v-6=C=s"n"^D͐8'ݿ ڊEBTPAEU!DwUIOep$FZo|놪'܈s!}q"TPd(le+ VW^DlYs:ahI`XkUq&HIR& 5 R r#F-M>/?}DLeJ{L':y!=lgwKsC83jwV˩}. 'v cU Q)I{W-Ly}0W_훰S%  YIV١gD7;;ZX4vhH;n}5>J13U!P3xd}?1mډwER`*A 36?M~hIxY= 28Lq,6h=΅Pt{k0f7?rFR8`vG<ؔk TzgL+VaLwp # &ɼS,Y~>o~3b!wcE. k,)O>e 1z<gT%5" V*1'_nFBQX !I'P!q`3QltStb‚ /<;ɖ?&%yD,eOp8jb> @Tᄊcη歿Zyw~?zEgZsq snݴŖ'2;͹Gz,>#QQ?_bNɆӍivnjj~w`GS^`=O3cM#!ȧtxۄ~.k:D!,茮?:At$6p9*> bi([nϠA#鰺Ih*~[Dqt珓j`my. 7e5/6u_T BXa ?-t :Ufr4RJJoE--j#髳,*v>&$Q?㰗.;Q ]'׬=f͚S' 3rxW˯f8{)VLo0床|`;& ޱ~Riqì^OMNTuG:I.AR(_Mo=pNtMj7#~s&#K( =q0:]pN8DG^>HY4׻]F# ÷,FhLuO'zܴ%*cvvd Elg:1hr35kgFatu~ˆm>џz9qLI)U<gx _ifmљ `.l8 sdg鶍yXWx6ݴ e}ư_("/[0:ӻއ6: l6%P,4 P8u,:N/6Ƿ7.Aߎgd 6{r0x؋LF"\b6(%D"`Fvpg!b` _ J*eK83|q(ԦJ>WR!&)A|r*2H8%ݠJe[|MojP?C[8ra93{cbqo5&0 4%eٳw<<` [S7߇?CӞ̶{"yPn)hAcWzZ*yb.urܚ[%XqᏣ605n'Ny'ND~ ^%s%藂]ML cBuJDO_D~_8;U\W#'soMgC=P9NWǐu0-ת׶Nnk9tz9MF̍("QIS?E@!&O">H@!}Z%? ? qx6rD.L0"*r 8"GO5E7 9?Е)Aֆu)~Q}@l Lrz\'I,\zӷyMڞ0`V+έxFGO_C?ҭm 2h0~|lClq槇L?dnOuD`mptGDVf롷G3H >F`h㖋mpM6\.f/ђE8 :|12ؑ92^ ԍ5k F?pA Иwd< w=6J@l^}SCGmr f%[ϧgi\ [x,ރ u *Ժ0: |WlrJi6}w ,i2ִi& ׈y|[I0C^ymr򑯎i&"Hm$ۖOvyxt)^F( buroQ i7c#RsMav))fDjL(sb&[sdTb1s_7牀:U_UX /ϭXqX@ Й[FAQJq#?)ߺ|V}+-H6aGtSxYq~ㅰVjhW#r#1!w48Q{n/i=( U-zFnU5˖gRqw`c4gej+6C9 ein33Ѭ1[wc⭽ҿˏ^.L\xK1ms\rGU5^4Z!Oѷzh3Φwyeƹ;R=}&z(6It} |ZieݲNˇdKۊ8'slj 9I!R jp%p%HZ޶(hʎҾ~ߗX;;<<4kA`6KTV2^4"?K/AnyܵE!JbG*/JZX?3ҹO;OCBp`D8or[Lf5~V;>QqJD>C\K7]A-aoy@] " ;vsHH'&!zXX5gԞNpCMN14^4xF~Fe21)^p?#fJZRԙ1]顕j3R%i5!̐?B{WJ-sva{> Zi9O?W'+ӼQ J0]zLBVQ= > J}FS*)ƉFZ5˨Vj p4]!n sDs43Q:pӞ# 'N%;g_= .2I_Y-,VH>{LBg6ep;kJW"u.#| ]H(PڰFtoQ,VXSTfAápuN\[;olBMEhZة>g 6 %ؑY$h0ggyX$^TDVÅ b$RrIh;,J>`i9 P*NJ}׌.GBei:㳙CB01Z[-OL|9uG̘1G\~;]kLCSYbz ɪ:QRnNH_X>҇BB),l}U1ƙ[ jV]Ҥ]/?ϝ8i ~%I7モl4Ub5˨5Q7Sߣ;{ȅ0N|v4-]$eq2\Ni%bd.3]@8m@n|7\9+إ29e9?G-n@@RHTlI[RV w=bCA9MVꐗ#bPƝ &bf.A@c5Iؚ =>,/eM|ဌb7dI~ЌӦ^@5p|n`LZAŦ*C}d.y<5PU=kR,5D«2 +g/ G32 S}r .qnƬ(^*pٍ9=\<,Q?"|p)+Fkrxo>.|4߅Ad )S:ƦI|*Έ qGs6;^O~+r.uD 뻐%WCAQTیuրW3egչ+HD))0:&p L Nt~NmyFyOs[ `\ky;h_e0@.ӿx9? f`/Z^}WBHRo7z`@Q4ΆбLwl_7^=t=SUZ7HGqgEGJ}9RcjB=)Ĝl #=v~xqvwoDk(k. @@ºk!}!HZ;wg_8}Vܯpt>׵>x4G;r>p<8"d4\:~FB/PGbfUޓJi8ۆݹuM5|35.axnoX0f1K4?szRG|{GgjCB*:m6H}Wu{ˁ6֒B-yC=Jۼ;&[8ի4|rq^9pH/U`mP<=cxOAX^kC]MIh'P?LqAC`S6ħR_h fA tL2jXBZ`͘piDlJALxfˮѺԘUА1 3CO9Ka|{۾Tz%E"˫T*7Cxvi2Vd9'a=zˣVIxF:x- i !p;m/Yp|x(~B%W~FA)1S~?E4=KR0j*^FR0*9GHg PR ArX㲁xk ҽ쯎[q-E%C!P L4"zڲ\̛_L#e"քDWTSҁP)ǥ `Uo~گ9,O`g ^O&WK50<0Ħ]oGp + *HEL b5pdL_RӥJ`wDcCl  n%-u'w8_iJqXl0kD>%K>gg^Қ(a 󬬔H΂l#*~)e,3L],.p`v:W62|]ţ^J+qXrJŰ/ab `ݰZ|tyֵB׭Tupm_%mzcNE(OD}˹ 8%ٛ /VaMr8NJ ,3R,w_V^Xk a'VZ,CL{TpU"2vh{^scS*1b#OQCmxf.{@(*Fz孷A6/Vfp'wG`)gI %[ ?hN}Do.ۇ̡cܴm}J'cy *2u=/6uX8hk lleTŏP7h:xXhxQƯKh :a׈~RF% 6.x0Fsu.VltOa.`Epv:VvqdE&;HpYs`Pk3$7LXʎ&x9ݾJR35\zMphg>0[Ġ[JNMyFYԏO fNȼ믨Zwb!;;kԜ9_]Բ?RpD,V]Zn 6yA;SkWi` @]!teKm&N̈ tpT ڄ?D!~mR+u& Z9"O "FBM&A J&PDzP_N"ce`:PK'  `. c YDDg:1JjrQU yH"6_zH7c aO2is+szDm^uK~ I\J lذSG8ӧQW}{Jޠ9Q-ry!pF}FKA P}%#2mW2cMK~??X͈gf63F{/CxU~hx_D0 D/(g[~=jGօFtZ.;NX8)˞93DkkpHα6A #}w{{Nޚ@gDvYv,[a%ģ5 ;nPs;sZ(xpѐ+ uG4߇s>=%s8Vo~Q:Ot?5'f=tgt%_4-9 \GpOϒE7s0HuLcW@BT]nyKfm-1V|u+fÏ'76g#wv7 /F)ˇ/Nw'gH\Ǩ^_9]>3OPh4\JnxIA4]:2p97i4TzYSFMa,qXKAJ9%+dDFرDBFt(LF_2du"ၝE9*D\5A5ЌoaZwmۛF^wLꛆScX6K+5gffgUߛvKsn1Qδƚ*L'S]+ ~)WOK%W '-3 YP- VhU<įV-"aO_*}3nȽ]\g=tr ?|[s*Z9 7ݶwͥp|xbhd}-P*vsӋ+I4dʢ|ciS;<|ʊ}帤F9}4d^v dy֨A2 -d8ߒS80DeDo[Ā=9io4gpìi5߾L^d)LX&s7tsX5KIՃ<7seajEo9'F^1# L9>kGYܝf^LMR_gSduvmySgOOgr[SFL8JFQx u6ʆez>z7Ʊ1ɰ]5CքяҡLؤMf)7&\ Cʓ'kyD=X!.MXuutpsر^oS*qT8l{%zT TOmػj:D.[>*VRnBU~Q{ڞy&W(Z ɮvk: (R,P( 5\T:%E5 k2U::fgR޳!Гd8m/St=Z `I;BVUafte 0)/p!cUJƧ7ŀ=d!]3iu+*4ƀ3s$\(Rg EmpX7yLCZQgin^Rvzi{U{|*͖::+wiEHaWq9UuOQQ=>mLi\@WicUu`̶V^eL?UITch|58rTVRmSTQ+Ř~cՎ%p"覫!VS`D/\d߄[ Vy!UEd…[[Fص¨ACV<4m,i)C;wf\Nr+K\ ֊lmN}W͠޸0Ӯra#2uSǼT!z؊?n+ks~WV_Ww>ҁɅRSI?;|Tɢqj5"#k U++A14rFty+INy0MYcXpdW>q++Zbmbilˊ]m`A Z^Lޒ|Xb"ku~pt8Bfx>[&cf0{ ]3 ̟y~&H3P|m][`7TGYrfn,kfx/oK_ *{t@2#g=/{Lg5S?(lK?òc!_03 γ%  ɰRO׎- Sm r;< ɪ)1Xɫl ̊%"a  ΘG՞v'bXZȝ܉l fm"&}GPX9{ΰ&ߐRasfW1^|q4t؍Dӻ'w'wTREdji}GU7c..}!.zsEmj1ݐ=0Z,SqK+J,q&ʹV )A{07Ы.B,=1ydq޼΅mIƣ*? 2|*0VB'G!$hBVa{(HeRzq#.Ob{o2E+RGqaaalZRJ-[~[ٗV-Tl"C",zw0gѬJƩ7+fg<Džo*pRGoҟ&%c^~[$[⑩.wػ<Gwąu  aDZ.n&EuFC~L_3ϐv5䙾/\! ̫zBkhy8! GJR^ό*_4>Sk6 A \6nLz#UCر-Wwa HII? 2Pj&%v sh1[M ћr%݈$wHd~A7ś? WaºG~*|M^nYRo^zzj=#[ۀC^WbHRo0 sdy46~ZC7{Ɨsݳǟn 8d]IU֝{6NJgnys]7,m9F7 |s湟3i/峹7fe 6ʏz&1>+aK;i c*kپm۞Ρѕs0HzBτ =gWVOR>#9~Vs#y nIUMR<}H$ո6K.^P}M̓XO__ ,!0rI]^H@Ld\LӤ)5mbIV-ZP+B35p%oNਟqoD 6q+uVhYᔅёBVӊ*b Kh.8̲6_^ddyԠԘ]B"ђ),i37ܿM:_i~X@,-Ѭ,}pa<2 8<|{ޝʰ~Ő;,j^-@d.=4cj u V%]8})Ϸ$'*K X1l8HH̛J41E!gy,U=U=M5账zGV!=G?l^3B_nevMIYdkۖg5:ñlfpl\C l;>mJ_$\?7wj=zŊq }Lx {oFQ.j.ZM]ImnvQ{eW`el|cΑJJbLsIR0)- ;UM*C*.T]< z]ʗu@VޗSޕ53J'Grd ),ꁪaWwiְ] "Fs-aאbJ:Dr1I'.J ]-[|:j6"yFvju/cYx|P/Aޡ\(.]VH!O6qrqGvX?$K q3̘&丣߹|d:dnI&.BZzb@ &[1㹞~_OG>բh^Q|w4]`]w`増s^toǿLψu)VBlNux$V 6}yqc<$^GVM)$Ue_y[ń$`xK)J_Sn@6zD霘1-=F]` P{7>0!Mzm)?7?yi XyUUêVl9U5Qy,4(/5\}?o&,{w)3]:~@}.m@k&^I'%ŏqi%O(5LA١zjq ~q U@JX g[_REJrbrֿ|v e4LECލf ?_^r9-R7~'rfna@S4S`@4z9 Me`(x$ [vrQ p AW_v.L1@!Cd/;)̡X?x{;T?Vvavՠ8mrqFߦt>_A?P5(~N{'\:o_\zʬc<% }[J5<< _yR6$kj~FLtɦqNDrÄ{ x!E:0r D8ҡhWaY[pq.pQrFv: :&!=QΊPXǠ&e":آ}0hԺA oU{6:+D޷32-my,ͿH[>`PPtQZ8f :gA QV*)Bȃ&1^o)* kVy,Z/XV˸EJ?mN+gjGlч| }kC_s&`4l-B!W;ZmH5ƿ+qJ(l9@ gQY9O2]:jXڠUPRbTyq[T|,1%g2WZBbhuaI,{bA1٪DP놜z|$X>tBwʞNjaNn6~, KڠuXh}y=HЂh$ATgwLa엪͏1axr Jt<&5Q)`6/4M%gooj, Z cMZpLh֩gGdWa75Ł"֨VFm :jYhڴi6͛q4eMݰn1Bt\T1Ux;$1HkhbĄЏH 1S[.s Kګd:IJ, ~~=8pӬٻddx &%b(Ns ZFsE=Xx-9FTx ʡ6usJnԬ xO*(^Ffа4JH۷}wI@-mR硢',(1&^D +1/J_i^F"5P0c#ۜzw/]=s@+ܳ<4-#Hw4fEEixk!+T- m5_Vq&[A)fӆ5,(>,_mW` Ђv9t͛ Eos84*O{lӧo LjF/x^ý^&SP8>A&::ف V7C3!D6d!X|y:E_%7gk]&TmcVO#P_3k*"_/o>| 1r'X>ҧ/%Hyӳ>Zj4һT@hnu/~LyCaaU4Wi@~dyGZqi$ݥ9pC@&sr<>K1ѿK;JD,~t&@84 -9Z.n}:Εz#dh! ǥkO[:!]Y) tdOrrvP2+2*TEڄUjPBwKΘ =|Ǥ<3n魠*ڿfMhsX>WgON'$u7tAұAqh͌̇D0'*&40< BXFFV}oq|߻Gg^äkשGNrJws`ϏUL:J^ ck@ }ߓM$?t^"YSN[yļ+]p}LFY>HCAqpyM?x MzA >Dm7r)y蒾V͍l1ύ"wm_\s ɬ?=OMfR5UC ԫ{GeHa[y =sD RUW%Rd1'=uR(/_ 9ַܺI "%;0ݎb+MG`p\ {?sX΁RKV7M3y> sh)wdc yt\̌m7x5~ngl4mp‰Ѩ!k ԣIdBG4CBs5COYbjo۰8=vMa./lnMqfJ,ias2`0:{Y),fs~vAt T12?+E1VhcO=B@U Xy$c9h hׂU ׇL_CAkHq> yJ--?I'<TJ#2v$d1h0 Y!}=nbJ0dN݊Tl_9V9Jkm{\n.ӡ>AB0fsfX |,c:k;u>CvFގsZLWT xc`d```a<=|EdՓg_(ETu=O'{?Law]+tw^nD.}kzՇ 쯍U}ɩo9:΋;FШO;XSB[xe#2UoاC??✼ 9Xz{w> O3E*De[=픖wE:seI5oÞR݇G=SBPs|W+Ⱥ }[0l]1V~ٴFoMr;'O^gLyhol7/ӌrq3}=vCCHF=ǡv @ilr.r4CүVldV¬L[eN0WԿoϓiosWwz:zQYY3RyK >?+#B|Jzj6]@UD-Pv>n໌u;WOMeFYг\l@*!u?'m '18 >wCÚ\fMc}~5lmo,.}Yr[Kf\yBGyoC[ |EE@ \}d<z/ |x{TgN.iBdb!3iMe$׹4M='4ri!e}Nҿ1H6dHAT8T* HGJ%K^ 2 RYHYRyr*УBTq"(*ѯTDSTuT-Iz jpE/N:R]ɕW gKnl7wSGG{oxDJ=é =Ż7,5w0@N386C&9^5;J-H~i >j^+zOPu//wR+=q v@GSLLgr _:KຈLzK[w˘Yu:sGXK˟Qt8;*3 s3=6(T~G77L4YCٜ9񜋮z]%q  ϖ1TeWe}(=drwsoWse \迎$H} nEc2pϘoKS }woZ{/o?9w*z % އaa/G|wywᘋɀu}є<m󐼇O~p)>kyg{ü,Ǜuβ(MxjAƿݤMk`RADݴ7?MhbW6;I&avk_@+@Uo'cBMH7g<dE ,p?-QvZ^SJr /gp}oyw/xGY:wLƜle>[.1[.bq- uyזK輵mwfyx~bbЇ1BL IvQ K^Ik&LŽD0fb`0(JfRMdDI/DK1Z`*tMƬ d.do<UڨUڴMr;gzpXmk'F}FUF]=j;௲Ki"bD.xB$dy&_jQ>º\ՒO-9"ZmWj\DI滎SidIΩ+Щ})dG»2']ZJZrl$;2VznM"L4R+_ ek=~^^8D9yWy1E& ϋx}WtȲuUb'X̔ؖ,O`ݶ5- 0̏1}̰Ls~N$ݾ}oW))L?nJ].ucԭRn4d 90 X ư l l [ְ `{v`gv`w`o1P `8`8VL ¡pGp p'p pgp Pzj4Fj-hClX ]p}p5C!D0· B.KR .+J kZF n[V n;N {^AxGQx 'Ix gYxEx ^WUx3 o;.x7 !0 $| > "_/W5:| ߂ow=>~?O39~ _o~?Ÿ+ ¿/0bp XaQ\qčpc7psĭpk߄v=;N3n;{^7c XAMN~?Ax0p qgP< #H< cX<D<):xgxX:6 [ڸ`袇kCpqq-x^x^Wx^x^7xތxގwxލxޏ>>O>>/f| ߆ow;]n|߇Ca| ?ŏI~? E|_ƯWku~w{}?ƟOgso ?/W?_JQ2i TaQZFihcڄ6hsڂhkچDv=@;N3Bn;A{^7CST!LM~?@At0BhifP:#H:cX:D:NST:N3L:Φs\ydQ$E-jSlZM]rG}rɣ5S@!E4G@Χ B.KR.+JkZFn[Vn;N{^AzGQz'IzgYzEz^WcAv#(ot?StZ~Ayb: nN/vj DUϝS۫|\QHn vr3ot<ϦjCҾk5| lIuw9ba G10竖N^O踍nXouܾ sTSM!ˮnSV\ShKѳn~mX=[ڡ؍bZGNXv3Y_sT+N _L:>WGAhӲo{ NwG[VCɩrs#_e=oNgy5YVS&ufLD T^n5iY|^~Hˡgs;'MI#I3>+7A:p}=[|y-N*y.orJqQYX;(Ck8>koqDWpd5E=qunk 6t$z"cÎ|١(S cJ)0.Geɔq:-# $ Y=f f-YVtyXKhQ]ԗH e_`~(5TAFֱQijhr&|`DC {nA9YH61G&Ύm/% iźAJcO wtCŗ^l4b&ψ8WV/g|%%Y]%Ԯ{M>ɏ63Y 8Tcx7V.M\7r8G 6CpWlЋcS\Ha /r6z#^`ޑ5,Q!^ߴ]&h#*ZL>K,GҧK\w>5]-2䖠qRs#?Xb9Vq-ˎJK! <= "4sύ=qWv/TKkXedI$9GM7\@&SJ5H⁚+C%)RVU)&E}Uc|8L h,]M hR@dVui(KQIf)EU )4>&<и+RRb\kӵJ+ $J+ $0, ʂ( gu!в1tmZ&akEX+V4tV !6dZC@ 2dȐ0a zhL@fϻ?PUTTPUT*4US^nHKhĄ EE|Q_TEE|QĤ &!L bnb܊BLa)$EYU)&)K2!0XKb C,a IIHJ3bC` 1!f0 3bC` _FYeA!0ʂ" DzC7DzC7DzC7*0!!!!!!! LA)S,z.sK"!UAT!"!"!"!"!"!"!"!"1)DC"JU۴41kƙ")қ:& ]2XbB 3Kooooooooof)Uzu]uYzRWzB׃VzJӺlROi);y4ҼSwJNi);y4ҼSWҴּӚwZNki;y5ּӚwZNkiͫckIҌѼ3WGؒ;yg4Ѽ3wFhY;yg5ռwVΊS&5&դtVj doit-0.36.0/doc/_static/vendor/font-awesome/fonts/fontawesome-webfont.woff2000066400000000000000000002265501423054503100267310ustar00rootroot00000000000000wOF2-h -?FFTM `r (X6$p  u[R rGa* '=:&=r* ]tEn1F@|fm`$ؑ@d[BQ$([U<+(@P5`>P;(1lhԨ)YyJi|%ہ^G3nڕ ͐Dp\Yr LPt)6R^"S L~YRCXR 4Fy\[7n|s໌qM%K.ۺ, Lt'M,c+bׇOs^$z.mŠ h&gbv'6:smb1بm0"ǂ*Vc$,0ATPT1< ;`'H?sΩ:NDI$T[b4,μ」bl6 ILi}ی&4m,'#ץRwbu,K vm_-\HHH?m 9P)9J$ƽ8~;rn=$Nddn!';8'N!-Jʶ.X= ,"`: {K!'-FH #$~Z_N5VU8Fȯ%Pݫ Cp$Qrʽkk3ٷ:R%2{ީh%)8 ILK6v#,;Ц6N2hv OOt#xTBfq^#?{5bI%-WZbA ^1n5צNQY'S!t" `b3%35fv;lά9:jgf?grpx | $ eZ($w(ZrSv+ZqMݙm?&s[tSSj9?| >G,bDշ^^:l3NA`5 26LpS Aߧ/U ֘'9\Նt!l PMR9n `(@ Hy)MdM 5ԤH'ґmSuo9 1 tØuc@]KRbNv("y뽻{ cscz&p5,jn kN!.n^Uu@|?v>rUaHR ՑI DˋQ~p ܍;;nL$t : hFCYTOFNN~}1"`a(?H \u0LԵ'͔PbnmOJl?s0,8xBBF_RiZ~e#jwhOc*&F6 Yq{}?>u.4h%g`& )R5H}ˤkܩ'JOI_qOb'HǟBYEM6v5NJ ONF Nx(1:\߫Ckcb8Q d[L(el+2u-a֘d5;N$"HSFo2i"\h7IfN8qx#v 6um `NM-J\FrDZ0#'ꥈnGjLچXʌAgYs*Y^ٵ;"$hb=ϛ0vH-.D܎Yd +^{Cm,@N<.VMS+\D+R|6 'q\T9DX<$p"酦$ҷ ,psTbNkI_` FWV%w~DԐ*xiy[rZ[S%Gs`F<ㅣ V+!+؍9ykfb82s}l;[)e$Tk)v9{uut޳@E>|C<\4%Rv @׺C8\~)#k|.ao00Gq0%hp L"+>% ^MˊNsq=䦆K4r-*%h#%;pP馔hC= &)baKL@t!~2S]rYlZ63ўJoOV;h&gO5RT/}{AZ&St ͯPC0D,pbpзz) ]I>Q\Bl"^3R>r*C>xPUz}Y=̕}ж 6-`/"H o&DI0E2Xa-{5< ,}``6jiim'w5RF,ч%SYWh6L_i샣=i13YI7NCpIĔ(r0{jrKТo)l3naT1\IE(m߃Dle$ÅwXU(@Ma"n,*vG̨x>G Sg̉"Qvb0*zPEyɉ?7$ %GpdY&f!a6|);u7#34mJij oOpȁv8jx(K/ZdxŃm7V_\fL7 pXzH7-,(1KHbe,r-pL3=T2t2ټXk:Z5spSsT:.]D"@-Ȇ!A2ɶ-F}˒2BǃQ)tç|#4|\㨀`fc,#g1:-ty ]2Z~.)nj%RK(y`8C֍zK-N `^+n3ϴT3tQأ4<>:J0È%ݑZab`vͬaT/ZaޝГIi W1_>)H"p |7mF^Z~f0J^I3V!{<+OeB#BcjL\-Zh[I<qv~k]GTD?S/-%ݒ7 wi|CIqwcWx /7xHO/o]G]y߃#7b$tR$ ]a7FѮ,n!rI|28x6gSh R^^D.xMMS?漞'G#~+v4d!FyT9-fVa7hB4,2Ɖ&vTHMqp4?R\Xa<4@MiHD_ EgRyMlTؠJݮ yc"HJ, 6u/ڴyVnJn۟H\PRBd|4_$k.w™IpS$|}j9m|1ߘn9395qS|xW9BVZ!mK/Ln;iu$*t3Ͷ@}B{Yԑz2Ju@a\MR7odze7/$4]^2kh$=%1IB؃ H|N.[M\ Lb1Mg:NV._0,+,ht7l8s~IV^ N˼Mؑjك- oܮůQo[mj=rm>~z4$M}z sh""u7V{RûݦO-D9V٥gIʎKLg۶BTP'K̦ qW֒3ep&ےLhpNaS w &;e(,-7vx-w$WnXUt8Y?KMctY؃p*Շ-БfL|[nL }4{5頠3᧌ n$$,+DNԄ-HV>HOs\-;W6NM8Fi;7k26%֒a],:!ʲڽE,{U nawNg.I9r:jFbKΨf)*cG5-kb6UЩpZMO`$WDyA߻[4aJ?fD?=d(KD䴱:D/[#$A #KH.:x?%Vr@[B$}coS6`LPfM&ɔA<:vÚ Q~Pw[+`+j V+R*ul!|+'KY66_ud}_[yuۘjo$Y=yjRi)bԋLaD(XUwIڻZ$7ڻ9&4Z'DF[N]~dD?VQWͲ}vS>Nm+SqHaU!ΒWb_+UO]^l59 @1'A^mo:9ףs-N:tD-zkSja4rczFۻ ޿xv7[äC8#7p5+ ~*bJJYzֳw+-p/LL[cgnlcaPHF$}9`\ 83Ym1b>~ƽJ؂ϏyBs="f(zKM"H`wcEd:b86(9<clݘ/kg G^ESE)5G_^k߇ v̚}T3;6 WvTCP_k._eєNJL{T!6j>h0 #[㗚Kz,!32:6d>himE\=H Z+{6@Wʯ&lC',rX !8(\̭2-P8h@C4 <~Z7j%) eeFpZ'15 ^6B3nco#~²qR@!ա z^Ks]T@TNT ,S*@7CīɅLiQN, #:RѪj91-YPN¿\&yL8ӹ&0cvƉ\JA;Q;]IM8 sMf?԰Irr!K9я8p}Q콍g-*sm~XP0dM^?DdIm8eCN}cà٭$s7ۼ#յR{b4vMql)vOճjְr1f4cs_%v%lKZNi+V3'~NMG@HBb+vVFq@ݱuKZhp@E0uaSXdUK}ԯ8GXKiI% uR)EI-ږ8|1GΞf6Ȁ=!KF6Qf[X~_j\^͋^k`DsG]~㤛yo};+i%N}Q0ԥUu)M[Z`"7 ?/[C{l)$Mr|^ a:"֊a l>hya{2>CPL j?ntg]S{UӇ('b'fg0ӃLPAMtd)2úY!v&`o2P[ aޔ5 S|#+7J #ȸ_dU6#VDB"K|)otkl,lU)ݹe5OyUAt2_ n53e*1v(K_HvVʉ3},ACUƍ؂Cuti-]`7]R !zsNt&̉̄k)SL̹y7$ϥDJNd"9 31 IZ(^( lw6 /@YB^}OT~9cc]{)}D8${yc,ʤ{tAW3zHImD4ܤUT3dID) I۬.d~[ -K^2Zc 8u,Y^\_ԁ_+cJ$\2:ZW bBw=[1'NYVz4;(fzNUf(p֙!x#L=#ŋThnba˳",T\o!@@sN%| tXj j Qo5oeF)o 9˷:h*'cJ孏[{ȄNfnz]8F/|1v g@J:YնNu:dhHo tM`R̍Ri:|N_P"B@ m`a:M  c2Ũ<ؓUOS\%a\Ap ꄯe\A.̰{wǿ~6 ;s2ŋ`W`TyPgee0 00}/ǔ;h[tGD5^E#hȍ:f? u3z0ڎ$T^TAhz x I{5'rK zo l֢,b89-:G|W)bA5G<*ٕ:ğ!]gj~O&UN뢹8 g ]-WW(WNI3Ngr3|m m'=[n힬M,?$HDD-O?5uX]˓37>*wg?*!JyT@ UgzI_7&\tH.YZ(4Y'd T Fs-qya7 [67K&J/$c/x[ᶏ;Īz1Fv]G'ڏQBSOІ$y(TS-;hűzT%Dts"=gwUuD?b$Zr9G<&Ña^2_Be;b~փ)Ό2j r8]'7 bChTd )+ mD).51-|Yy*oڤL 4A她= T@|X$in.KI|R@P@P*ak@ ۟=I =l[ג"hX0QҜf˒펖c<#9`|cO}$o>eX<`,o_K3 p{YAn[9M T(!"?Z]iEm Ğ>'{Gt *~y`'A?٘#)o($ȉەLvYO1o_& .mv!*)$zmrt(:GGbeVwi$CO1 cZZ0G 7z@Jy~p)g,gYL.$, -<k{yc*02/q1gKM&R<7xCy[Mʛ #ͺDya3\wfwrFĸM] \NsWݍd<ӡW064tȴvȻ0>ԯ ; )f#* 2<h ~'Bw mH/wqMog C)̵67#BS>_-[L|RRlQ}\TH) 9Fa"^bA:ݳQ4' =sO '@.Y&8z ,i73y;U}p/I  xVxilFZfhXc.b B*|&|ge/kuv\_Hb dpG/A}㬬'xȜՋ;E !Wj{ZI$z{Op;x=׺q{5l23O =@jj# GYTn>&ެ#CBϩzLuylSaa0LTv3,2 sdTrU}El1z`Xa*h{qiuU\"Lд@TXRUFg]sE5V0X/ukzB'كJx Iz7YΕ1tyΚ_}|xm[xJ}zlDVrcsdsqv[&`oUl?<jC! OeqB=J\`Lr孈d1MhowѹKiģd*;^ҋ$xHUU`]GkCꆂOQSCwo g~yG8P{{H.$6!}d4,q>`llUMBRPe2A1RHqlBQ$W%bhBÚV@(?FAQ}dl+bNIMdT"+ƌo0`89\|5 ޣئ(y jqm(<\G 2dTP0$n@ Ē!X 㺕Nkճxikiݝͨћ"0?^2XF,{sr_e@Vyg N_iwq;XED\b1G(RsT<\ډQ2tT ; `[,AkKbDl#b8,]i\|kCxLq~r Ά>|žBab?aag30( j"FA*{ߣd]ř+XHzs ZSLu:˅)ҲnJEBnS>Ħ mh,RT~}9, /.H~!`ExOۖ mwIl꧴ёUzzk**|m* .?~ chp?eY]*H|̛1e?V; ا 2PQVlW6m5O3'^x,ҹa)TeUs10ft9T{!L@OLtǽ!^L!ti ^:CR K ?2TYx۩Fq#0 <hѭ)kesaTl x9d%+b8XZ ;gv8 n7ϻa&^ob{w OO7jϯزΞ,~WYػqÎzVoλg'5("ե AӃ[:P|Ӓ+>#2?$MndueSJ%e؞~Uq ޳҈zRnп,7˱>` /uFgOg)PJ\)Xk VF"\tr#wE]s:Y#n8 Lm"6D VġH`Q ௢үQkG ]<2N?U &|a_G܏}di!:`Ⱦ[\,Y]JϹߐì~OA%> ]2Pl5pOѐ[ʀ4O@¡,Ҭ-,4X7-#?3{M·C18aY)M"ka_=4JqM?nh6kɜP  2;3 g4ՍZЦөGZk(m pv riZF}i:/czPuVQ9E&'/ v<2ۊYQ)j.HN11sʗ؋{ '|klT%1ꪋCgQUJ['Uֶ̝ؔ{81 rnҹ} :,й6X7fe' NM2p|4p6Vn듁p&S=[- ߞ~NjIY/c`YAq6-Y30#V~hsEPT;ub6WD#N1o>)ΘCx4$/jl1 y./,Rr[YE*GЕKm/|7 SISƗqF㍹6:cVs @w+k1caíw0 :Y5Q" +g"%*2t`Gݴ f:hN33^~yө)o)l*H-;+|+[-ZGXf~Meb75[ Ho}pi8;`$7~Yw4RypJs}!*Yf~W]TKV0Fyl$"\AE?W ,[b0q.|xZ/ˁ]P*4$*(R7L&`goTܑ.$V̇hULHnei_"o߁e*mbD2u{ݹш ߶\ؿZDܚ vz1UlRl-wk2VxՑ;؀400=ԑx~޽ګ o2RmԔ=_rZ&ן/߸([C{%b[f.\l$}VچU*B3lRPf d'GLc[dN %C9X5h _ cҠW?+`ރχ#CBW'B~cb 5~}`AE((r{2me5 t>`vd, p*=ϕƼ' o$ݥ;f`̢tɟJ$HZKԊk+LmR21,qF p̹-J%b=gV^y~׼0~-Pת{ƛB2XZ?oG!xn.}%}Oo _?bJNv$bl;z`&Kx^]"d+g eI2 B#(ijNN>SwF W |b WoW^\q?1>BL/=iR,cykWZ)BUkjy4XK, 3 F9pKuշ q@OAvyG4.,m#D"^ѣ8lQZ1C\4oJܨ힊dD6h[|L]V~.:0z*HX,Ͽ7zUQNe.7$:.0֣Mj9g {2ڬCO墸N٘@.W1Dz[[M%V5r!4&Ur s7%yNJ(?nYm"TCMmr.ݴ{bSNT]*}v`1^HvNoUۆAS6WOىe [(B͝to1bϫZH{~N}Vˋٹo<>#oTFD"%73.(?f]`!1%UqL:蜧ϸ|@8'+VWu۠0 } +T/Qnl~c{pa=V:#vm~1t 0SPH]/jg/!{/c jh[=U@ʍqIg6Mmq%Y8dc`"Xt>"{riPO?0=/9FnV}OY[՜"I {GEz `)ӇrOoKY꺧S4;L'>cN@8 ʋ{삕zb8_xV(X"]ΔěM6w,fgf+͜)TJUt> -]z}o*mGŶ1S<۵&:QzHjljL F,aY"'LˬɴbJp{6իh]m E= ~fFvE`EWinux8!GVY??7K^+[2%_mwsZMZ?vl9fO {,'9/} T}6VzôvU[dT,_uVE+B:xaY.L4rP1"nj[)Xs54 4sS6 {(,kW :Dm3/ T*z'1o'3ow|Ћ=Y< aDm?F_Y3f^Lff'@&M7F0{GTB/fzqc].L.In^Wk(hc!Ȝ|%?%\6Qn*0''Whĩ=ŝLCgR񛙌9V玫؛AӚTQyč&i٣hQJ,#|d驺z|yYH{FI%ORD&k' (kͷ_uXT4JotǠ`X l/-ԩ TBIjԛ/ Jn0,ħXBUHhFe% 6% /:&zLldKT ^Gv͊SA4:DIʯ< !.1?nTzhԓ尵 ZBCnI~+sm8T=f!c(KHSH7!LS.D4$~]ٴaGsiK7"dϸ}|{ܰQ7r-ŷzRaV]v4t2-讨YDیS@%_B(FHke%&5='jF,GoW9;(ڤX3z`fM<~1bR6t0luFIj˯JoIqĴ(cǘU@Ѣ#e&Vy( {̧KuWKeZ ^>(wDI߹}x ƺ5gYG22& sσ!q\ CP%U fbS'HLbi,sF67߼D g̣oGa)jS-&>7yCCΖi]MR A0 KfF=z ggtf7Kx [ L^.[ԭ>Zc736c͗qw*CCV<])E9)ϛ0lSM.$bASHib%zqݓV޷ʀ7+8{ \HAZ#[80* r[-swnxP+HElY./k6wKb?88GI. ur޼l9Eiޜ`"ƃȇ˺&vIբu*J\[^enQ%j ?{nW+1 ZC $3!6/SG @4ΌE!Rd8hg?J~u? ZiD4K{j%)'xMaYvkEt,lc:wXk||2$.Ey=x*-LM_xC{t4.<Pr͙s1/N8uu.ӿS_rj]\av^sQ ZŜ-DuSg6{${r25>, hcbJ֊?${ouo>ͨvCl(N9ߖQ]}3( z^)(Үe}E1\pB(yf̷HY/HI;,q«=d&T<)3SfV1ړ'vhD n$4n'r}b0DxoV UJgIN}4/|ߥ\$My"j}jib!NӽSBvC9wp7}5q2ѪҴUÍ,鼁I};Y͜ȝDJm[Osޥ$FlX~=/_SLJ&^( qwv# ꒎.P:bBfV2qgnٙ l8VӅb0aG-OTlO=AfWO׭OJ{̑Ͳg k:I3*zA$̊kP `nFGx)GRPE%5\}3۵RuuW-2G%voMk xBuFN7ׂkV)12dB!4 . N8O,f2TiV udLzyug’;Ks'^y+7UUOBж+$%O9elե*c@Fc6ggMU_~1fvV5 -V 0 )_D{Գb1#Q|k9=?Pocs$&}BoWT"M =Dy$,IN,چ wIxE6xnCC-,ϕ̲Y :y~ʝ،=Yc,TxeqUk*OTq\E*/ؒ/NSUf:b?īHt$ٶUfudH"$2kQ /WiXNx r6_y{?2ڽC~{u8|܁Sf+{30`wbcCQ+zƪ\T-{]ξ6Ѯc?8Z~|&eD9qW2R,Y+y<`OwAbz6|]:qZOVgM̥ickJ0=,4,am"RC#,c fZ6RcGŢ:)e eIr6.Z;P+O)$\wIV(h`z{%fpxl }onr 7%ӧ{ xm1oВiq JO'V!"=$ ї4KS+&Zۙ'憥Y^e~},x'"so߮d߽}{.kTJY;ffjKVB+jqMWL"e/׶߻YfxwI:kIq.DzdLWim]ɗ] f)B{lֻ`j~ކ;ā;~7-zAX'tbWO.$GS0Ra#QPO|P[ %`C)c"ͽdD1xp_s*5ac]܎*t]8Ju׷uO աH>hLkq7gR2,ʪZ]|$CZm qX LrSKb홞%H/w>G9(|vvNnNvX N Ѐ`p+{(u\ sQp ݨ3q\͟$ﵧ;QSřz[jl 6n 8DT}㔨PE %BWحYw.!/^mdSZ~j=*Qgd⨎0t]q-.PJBp1 ثatl/ypq{~TOH6 uNwY| AVrwDh4Kk+ /@ @OJZB1[?l{JՊq9PvoY6CJ$H`7Ei)*eK؂Y8{V)b pNv/A%;uh(w̃l}*4y|uV:&*P;LQg*}OW;xT!F[ o l*KKUvܼƌ٫NY4$Gd+3$KVZF&FuRj.GNۖ5ƴrevvvȬ2MC[)|eGyb{)ڻ.I{l1CesZthɻRæGp7?(dW^=  &fV͞iϟ\G6$$uP=ou87[%>`<.$MtӗB)GjSQUd`S"3ɽ}MױT th?7]iEHzş|-tdۑ,:Dj7lD6٧-+}ZU4^xOݼfQH U;"I{)1Z.@2󄖩b+qzV s^>V[ŵ-5v]蚮c""f\߬<ۋcy#Qj6dr#ȑJ4lO(yN}$m [-|Ԉ*S\ќ臉@ @ ie'm'q$s'B੻Ad).* _y#z_Ы_{_a_=+䊒ӌϞ'Pܺw GJl.rqZvD(DCG&Cر!=ǣz4v($;{2 @iǘupcE  hh s> L^fڻw TWޟR /_IĦM'B.,P-Hj)%PDp2^^w`K֫KPa>ξ﫥jϨg)KSټdGFYG$X` 7%ҀcKQO"BաB'^.`";GleԒO^l:Q>45e=[7$z iF\*B'ǝA koMFc3|Ӭ%v>!]€'! }:xi/xcR^WICz_`~cVFvf]5OnC?ҷ79']/g}փiUIȃOt̒?k:[>TSiE<7E-N ؐw;mDu[z+9g_PO$UYN[#jI&3\e4n)Rvcx/VC?Kg{GX"b(6 ʛ| RrI&-Nձ*?2BpEYP [.r?gOh/%lROE f N=d&u_qb? X°f:J/ }?(u6P"L~iV-g1YBg  }HK24鵖r)ۡ#|ti@@JR[k xcE^I2߸dVoqPkZa2H/=(c[lW%icXchPq6cM? }iShRm]6;?'B}gMmǞCj,vԱ>G+zYl?Gܦ*{.m7AT^1D";RUr"bhlqw$/gyRmZp%0Bϝ#4b\q0n N]M J},QrQ*ͯA\')yz'KdخDWdi@gzu'1\}^qI<>e^h)Q*lzBl? gGZ0`~9/ie+UrWWs6 g*D}zyn+ህwUӋ։fG%!L[#"h2fmh|Fqb}*H#znV˴]xA 1mk ׂV|=@=OBzPd5Vrl$ZՄ88^Ϗqp(:A6J5PY2 èV'Gpe᝭\hjp1awʓSA$|HE#7ч|p* `D]ZB-\6iWẍGGG׮~YJT7Mq^#0õqb0KVot[ Ֆm^k k-dpݟ^ Jd3ݕFFTϺۗ9o\S8qk"σxL_:PLh0!iˌ{8:zE  Oy/Иl ,)GqQR`\ J>[ip&Հ@ $:Q8Bt:@`{>'aޝu99'LcиđHhd͞YGf/ N=Sf0T;WJ& I 231kÉr`}A̶d@ \q-9(B,vѣALXqH[!f-t|nPΤR^bGOf=+hWD;Kfx1^ U]3@jK8{V. "k5hG¾pC鹒*6iS+пu4495dj+ KkNqBM++?{2MNJVu90$#dV/,) Ak0Ƃ^Fߛn<%Jvq$d @ww?Rs D1F-_E1}zcƝZh[$&DWx&fe% ~) ~XLt˛҅JK//(F[ KY=;ؕb~$Vd]8|bJ):v 3RRQ}˺O kUP}SV xsQro3z2F'֯nN?{"]1B+յ ;* eO]-N~2̜u%l(Zb9Mh]Z3')9#>*%)V`leY.5*D~- d5JZ!QӦ^fP/fj TXX&(f!Ý^g/j< /륃S'J֓5V^ ߟ^m{2; 0i7$&⩵ӵXEOSx5DZيt"hv_CS~A$<@ f\;Sa)6C_Ί g0(4i-k< #5t\CCh> ;!` 3-6htD]SeN }}"#Qn`F:>79$lVe~̈Ja%q~ܣ˴^lC f+/ eBa<' \*FC;|c ڀNf!L2i~<[ p&ѕAknnr틧n&fvnjn-25(!rC~D"`\T'j P`0iO͚Fkrfuəکj\'3!BIElQ?m12pQe>RwتD.ۋ XN#'Njjо4!tK_fR!@棼CJ-jaH* Np@wV[; ➄sqHlڜA?y "j!<U?hk1oa޻e8S1Н䋄!9hI B 9Ko_([f0o! 31C;XIh$ɀ禹@@0Wl ]&)s64wY3c.Mg ^1Oqs#Ms3ZNLMi} 9U~x~{$6FɬQEi2WvYF AVl VDXer(ZeͰ3)\t5\^"rШs wP5f7NK$f^q{"L]z`@DQh6f~hG5uU7G~ .#3P TV!nژPf6Չ>l6 9@Җ5Ϛ62t@7 L2  t'ԯbHԼwWfɊ7=.=bx %d? a 9epHҩ K\ۏ$C%0 ntv:M`᳑Basp& )"-qc @Ibk3ePF8ZmUL((qP05n'CVijɿX?qg^:ӛ[[PV8 6=Iɉ(cG@Lb!ll8߬MvvVbq~/%Ii҂ϡ֣T=!BPS:muvPsϥ;Z|s,G:pHgVuZR>f@e⋮@F<6Ͳ.L /)X 3"LN>^m w'>\C]9b Jn)snt__xEKD B $gYAV>g$%L0L#{&ΝFtd\P=a4 8"<ܝsL^^NEcvH-_>֋;|+c! 8O/.规Jn8&,%st]6( kH6 Fq#(ۉ[y{0(^ ֿbףŬ &fzCqI<Μ$((h\EDCc_x/E.:i^+Ο1צ҂Ji4@`lxNL$搘6T.?4] X1h|}g8<1Ȥ< @K// 5pלotpa jtbE Ey&Ц4`د$L"Jvi ljZ%=')8e`8T*M8.w~\(Htv r"jDoGG ilHe%ia&9dd>-i lMܰTA$VHG| $ :1R s\Z $Pjۇ]ًg8`簆 zߒVXݕxrtX/Ap2^[1~R{뚬ɇ:kCU'5n%'CXP06Gۮl[<NscOFeQ-gi$RNo7Wz _t"?z6y/H}ё{qL$ -a[st nSn2ğ@ѷxHNp2& 3 fx) WP'h7f> s!;p&QcN>OgdHE1u {^گV}2@JHS>!~L^d r5/GyNW-`ɚLJ=(RV2ȏM;:-A0<Ȥ L1L~.ܤkgLinNdu'f]BsLA5ShKvvn-_e9eV"mB:GΫxcZX oyHKgT~cN¸OZK:bA%9C ]oʗw1)(t^?uƦ-A99NلL#A2Yu5/_=fqljއˡ?uArZ]AX _vM1V&P\6X2m7䥱[lҏ'AQ6R S Q}딭SeS\D-wLrTC]ӎorly݂XJ^fo - ˰(X3R>\# 9VP饘QՐۑ,aeX#*gVTnqGL(Z)oMi!#ZH.$ɀW\p*ȶ /.gy 9L2p(#Z-)ijjԭ=0b`n0a]k2I)XE8fnDη%8CS.oěNg'dp-J=aYɹبNkY Ե=fNH^f<(|E(SL\>u4vdN~HN[nDeh/ڈ(21he_ʔQnV=CHEgi~%B15czŕ v >aY%e&c!pIB 8г]~A-l641/[\\Z I T4Waa8'lxRYNej3:-:G6vad$$`M,ܔCz3!q1]Ӌn#xBl]K^t_@YugSk]OƤ&v:NaLewɋ-hY}:xi O x|+^ñCq%]{[[q" x@LupՔj -[=ئ\ ejq[%^W'Hjyc%J8Imx=C/].&w4D,Ƙ3"z`U |M:3Qc!_ǣW(WjqS#f(G4GޗI>nڄE٩^˗nHG[M'C&Ǹ'orUmNݾwJ?6\AbGNzŽ2qXDIa'HVT으Et|G3( oOtrJls<;3)YQ`gw8"o&7>cѭ^@& tT}g$}0hh)GTsy4r o MH; Φw~| !( ad" -sQg#,1M|/uhR-.k$GK,݅1a=aYPA,q%! ONzvN6^>ƬAvJFӽ) /ުl̒B3GM'[,n\\kѣ m1hmo>!jM0C <埵ߎ\`K|_xN`ǀpWJ jHLM<_=CM@Wޅ%ꉷdžf%Mnp Z3@>'Md Y,BTuJ:o>b^չȑދGx_W`H "=ϟz&=@%ӌHqixDHXxjꄯK |@QTP+:uc}ОT B5ڨ81hȩaFuXLc[nNרxtNDX*N8s7|2 R{>}78.GyՂOg#Qq'g fKY`9h2„6$} (T?}A`78LHFRG EFJXw!SKr@EKa2'ʌ%v[؟[7SFjj[5hMt,^i#Coq§ZeteWip_t^*>VlhZQjXB㨪9q7@'[=eH+^їa/G6z<6)yжDHwFv2nF)%d.)ەP6^÷r {hj)ϰy"T㝼jMUd΂Mݱ[Dg4{+ݝ:<9qAw L}A=£6۠evAu+U_Q3f?R\0R R^ ,VwW2`A vG<9 4nX;? ?*uV0{[4"΂,qӼ? p}_gKB_ %_g=Ih|.ݥąV^1䓺0 "{7ms9ꛦBNIpi{ ]J :My%uGVց kkpyjp:G]Z$0 _N+M7Y2l @x6q 459OТ}Trf52k t߲}pU\ursVlתa޲ }Vm~3gm,\7m}-*,EHq$Yx=E_V'CRiND9/ Cbx@8`2I̪,!f݄nE8b+Q2쪘CZ^?GVf砱(BIe+9: A  v4RBH zѳy|x֣W?EtFOܔc=1E$V(T} rY!HhQ!.F/ dիG0;j86t 8y QG/Za3= O_ؤJPגIRsZ=|ڼA##su曻;..tש:KIT'6m7":sbqyL@Z,Y bg,n{O;]ɪ!_"=cӺdij2GBX$|i!*nT%;*^3/cEs4CwLj})<(YpHwW^HL- vpđ@wПp̹UK>1뷀L˾f0pΎ=_! 9q[ƭt-c\ @q]CAJpPao|ylN{F*3FxLTv0ԛV,jHA(\xxtP R^Sh"HJn#_p.$s2iB{TuZK t\LI%* P={b"UQ"VR} >Z ŊNVݮ-Jh σ ^ ;FQ,*+""00):;:VP8*e(7Jl0oHe^Ɗy%`4Y[eX}6KJ˩^#<ɝI_/23-@l4`P=K&=.)՜XvLfo BG]ޮ+؂PyInV`k-~SddcU.gƗ' 1N0P!ίH]Hf[Zx\. +\_4bOv#v!l,x<DxIN-Fe,/\mdPyIrǐ&$GKKև1qzG!A38̍97U;ȴVeg ݌LΐotpR# AD䶅)m"ǛX!-ΜaR_});;6П(o:֔qC^Ǖ۵A=zOb d~hzn/J~ǪŤzS,JJ#2ŭi Z~_{c]obR:v:?e? tZ]ָՠgժMk&zzq%UCW\Yڻes7ivZdTVQC$mČki wƿ#;̋ %yG8@5:yq)|⌬N=Bց^\S8]]?{rW[-+Wq)^2-KK0g4LҼ&OSPdŞ-m>nxQyY崎byCQA)BD`<`7%f"Y>ШG]T}_T,a^&xԠ,v4EpW¶SANⅭgj)&d 5 4($sDBݦxOhXQLw`qnPsTs'@Tz,2J*njވ4_}3יjҫ-%i POF?kjS#G'p1Jmba[2?kKq!@-^Y97*o0iMl=ߺ(7g_ǙWأ.. pk #c]@qos]vKi]C+K6 -/'S{VF#pƦuO&gzutxeL.vsMfџ@/)uA)0!۽)/Y_$mU?S^ GqVċj.vUH0mǕ*3bt3($F#PhzZo\d沠pmL~L jbmmK qsN"Q_Qh9 -㳟CUџO=ކy5 YkN.eui#uڒࠠp *!C_߻3Qpazmg- -k 8Z莧YPdM`TGhѤ]:dVNvcW:w|kҁ.:ӫOڑsw pT %z΁ه*0) A&3PPQ_i.-Z!%Ttf3k״+f6 6mPяH4ׇ2 umMCͥpm*Y˭9_J[.9&,rHi߃8Ʌa[Nnx J#u:nY}lzӮ^Y;zӉ1`7zv/_眓{='T `Jټ]ȇU)K{v[՝y`-0-?^[mSƐ=O#_DqqmR0) ibJ}I克WTm Aj/bYFNGuc\:i%fU,pIp ^yBcx2 Vb6Nd ٍәTlW{tĈT{S/QYK7#pQcGogQG?e<tJ83YިF^:̊|ʚ8`r}QhF4뢺j":k2;k.,&zTIFTy=K;pr$Ѳ8f_TIV[[ź`.N0U8IY D57o- !mv9\/KR!6 b\+'Ie/aFzͷ{P|w4ej-t۠^\SK+'JRSf4Ԗ+e"Ӄj\ʌE.>p!\B}vچN!"fR0rG߻* /J6Mn~}}<olϸpf%n~WXUlA!ˍ!ӫ8iD*z3@EYoJNC8f,R ƏmwE(iwLe7xЬ2Lz B,'\n@Oޤl os4PcXY }tp- yC&z Z`7)T)0jJׯ$7 ۷oUckwY;8>+g6w&$>ނu> VZJg˿=>Oi]@QYOƽ AIN%F(Y99JC4Q@J9u3p=0A1 ,^>(HRBxLԇj-ap37ubNV4|u砋ale zJ@5yCQ@RRqO¼p1Bj*O|O ,0߰ʹн,u Hs5IJR(+FL?Fh#~J1 p)O"-Jq Ƀ7u6(ۄ!P@>Á1 &'s3هX,9Y|sACEvp|̺%37_*xC8 <"'"G!£V볩s&<6D-mttzq5"mJ}_(^m'Vs۴F>}*sVӇ"m9oq{o! <]w@a#aYY}i|#r\I _ߙW+"푎Nܞ0|98ֽ .yfnsˡb~p*5E#s vN9>cQG!Ú8Њy6& -2~Q[aṖо)5_[z_itb(߭O=C/ P4? 9T,1լ9"fP]SԜ(0v4sJsbnQ{} #@ɏU^R+/6' Kh-Fs5XޖXyXQ3 WKb"&â{[mpZֶ/ʲZ[Z-l$NeWHWM_ Vӧxs䀱X )oC&6lktIp].@?wShs-$9nP[pYӲG:Etb&< E_p0JtzX B.R .EĎu-0OSBþm Ǣ]vd`ÝXP[ VC4O0&zu4&Eʙ'tAB%+DˎG~AxCPKZnRgx+i|oʜ8oqJ`G~ ɕo P 8yuq뢵𐠵Ռ=ƶT·n2paA/F[ ]+p^F(?ɬ3ggQ)ĊDLm4G;?81[ѫT> =Q8)ʒ5ck+gdRA|vakBcz[C8^'դOS0* )5r|Ȥ^?z}[SWUT}?LU^}L 6h8 bǎEڰn/MA66Mkr0.'})X "9O ~.7@3_~I*`֣q^ Q(Tߠ1``w2uՓأ0F(zcgsSolP8C4>@e1bς zF]5Qƃ/Y vAfGWJ;=yw@Rq\kK0{2tv0="w 0Nr DnJ`37%/-*R.U+[lQ7H0x/{džq8>6F'0*G\Qa$;hfEBC-`0)y[hʑV H2pCxQP¥9>&zgိ*+kɼ'W_~IPg_CO{b̖aշN ~A'/I팟o" ܬ*0wKOLxi1M*ˀzܗ{ meJ!,O'Z2Nm:ܢ*G`x]sҶ#fD\FIHw]I ?7#ȂU.5w5ɮR?70:3np&9&VupAFsUc;I}!\Uv}bz:9y! Rξ N@)0ߗDd;(AXr[BNa+{?X/Jڽ՜vݶ6lҤgO%P (/V j>MTc74bɤ^~^()yIЄe7a'xU$u8/NΨ'nh贑51;^n48ߖSqF; Jx]]Y MG-WM_ KVgGg>W&i& əۣκ5XnF>gla⧲0x){8}>;|9 i 7?kNW APEjpYrҊJp7~V8o? 3#JF ;Sl6QAiCfT0YwI+~[kB41L[*;/jLAM0X}>.tغutjiZ6)udn? |n4oZ8H/h!}I>d _Y3rDwc6ZKجA;T GXKb4p:I9m{#?{X%CKM;E({vT6 LaY}jOѭTв`u Jۃ2f1D/MR1Cb @#^$yH"c%߀.MtBl7 ^]]]*eg^1: v"t2=M@f]M̟D_w`tјmuJw"BhO;ֽ.w3,eJVKmC2LCyӝOLU{/\"K h bxZLRiO(=|V})׾[[P[n26YK UL}W0$ڃR: O3Ij(ΒRօJ )HInS(gKp 2\oNya軚8'p%KEEgO[:*׸pⳇWFt!Woڧ"˲"CրooBJd;'K͒__hv+ dލ 'VmI.^˅ 8BsfG08ռ*ʮ ꩐Tҕc6s~JimxY~V)Iƛ+hΜ;]EBAАQl"U,C)'fC{KD]p#(^ys==UjonlVeuiJ+$dU#;O ?92 <;q>o Trx& ['-xp0j[;3Iw6N?;K9YR2vrD3' KgՂ?h?r_K& `t͡񟞉y7&.>tu4ߛG :^MpvwڴYz~ڇձM٪!RWd;# ^zʈQ t\Wy\OJ14:5\ SXT ݓgvV9UkX,miM\(n>EI aIi_,( ;.s)=5AI(wXg}4YDp4{jq(Q ̷ZJUZfK*xC~p"2r#$!JzZY.^|h}zXaIEXgt^4R{fLypᚚ1ި|O 25" tUAޗ@uRPNX1ZN/ܨxIQ×_y6EK / cuDo7դ |2VC f+H :`wiy~wkt@4OE],<ͦ?sb1- JAA2-=t칙Cõ̍: Ba;WCEΞr{`&,'t[8qu -(J]4 ʹ5ay hhY.4j&4a q'( 5sXGjWB~cm۶/.6a_A5+=d >Ĺ_.h8tBs0HJll[UH4v. >]( k9. UA:,A-wyʰ҉VjVU ^}|wTHӘ,Aq0;,ZD*#{lH7bRX0CduBѢ5d=V\T=Q37o qA̐AOlܿ!{_uD G_rkߘT^}Wo).8|gWPCeJx6N(~v_;ΞS?W#M˿^SmG θJQ50 i<&+;V=KrU e#,tFjëΓU|N'uLx&) 6wrroG4 LR gnZa#t+2>if!ϥ)Ǿ>0$&qqJY\IS(ˤ7^+'wٚze !e-ݙ{awτ K"Jd Ly"FջPn)ж w-YU6L8"!ѡ|Fj=cȠERz!z|%%N{9c׉S'I#ܳ&QFn๕ !JƄeeo},XM0cs9]e08ux޾B䦂@h~T$% ?-&=EsnϨf'$Є`9wvȒߖ$sNy7zԯ3.ɉA>c,vA?p-?#Gv˧hm,QvG=KԾ nk@p*;rQwZ*ړǤ 3νեwR-`Qz\ӧvch:pZ7ןg~#;xDtO|tҺ}&Y9ƮpbuU[]Tι#UFo~yեj`a~.;&\UBD<j5yуo)],+]*D89żmSTI9⺹"_KKgh&\^a= X(u`mgO,Ӊh}y$ے$ E[b \ڊxl~[l:鈼,g\jgY '&f)GL|ƭ*Qpr~;ZI] !q٘ >0S|_Aeg<28@+5 3gKp:ELBvKj:*&z0V >GXCJIOErWb$W+^jɒϖ6HX#18 ˌ5ԋ`֩wGU,03 ̵1 Q&g;!]vX~0a \MF4C&h VӾӗ|怙w9}9/HY1˚W(u2igo}9~!V7;:H xǗ~㲿vWزj w$kʪe1Z^W$S+ļњ,-3!cmh9% Q*;%_8FV(s߷f8dشgm5@@7V։!)^`#m܊Gk!yu訦(+q:­D݉5/bwb+bᎁ6}HЛm$te1-ě G]iܘ$Q:npysǩBq8Hr-;-cN*rJ]cGYucyUku DQ):4^K<|XEޚ.Hxr亞jΚơ-]eU6xbk_loⰯuvoLzA+$^ҕ\w%>[PG<2FnD!$Sx8;;(~ Wou\Ht*GĞv:[Lr-yGm k-6K=9D>GkaDl9*K2J8OsP"偙bN% pxcN&ay{Mlƪ3#LmN̕&>4wՙި|3}+e}_,,ALu[ϲQJ5'z@NԝZ̉ED@(PVdl\8N&,)I]dNY8+ʞ_wu⥊8#+1d8s6Ǭ}壯 Uyfc+!)Ȧ1[N}3ǮIGu]x~^ʔ4 qd[>,{1#^3ID=q$%ɥ:A*Cg R@ BH@!Tnwl˭a]ɬz5 {z1R&l\WџgEIّt)8RTp*YMڋFfR8VYbJir5Fč N4egH%<ټ njc*v<᧼ /Ujao.lGvAvPؠZj9IdAvƉ< jO3j5KhiMt|en*=-ABQ׍.|"?Ïs\Z%gt2^L#;K0>;!SSI!!H>S|BϵŵQN,$,J,ya>A"TSMK"I쫈+;;Ӽ[5*^1!;m--?wb^eCiO{*NC/.Ms'  f+vS'̘  TkOHLTpRs#2Y@2N6^T)u[>4(n#*w²Jb$ȤFTxM3,"& ܴyWm k!o , ˒e6GG\r]U2%8WH CQo娣)*[zb2nʹ.CL?gl2\#.WY`WG>r8e1jB Uq8`{l_d9)\$n +L[o"N>eYfC-\Qz%seg@% I^؄*ӬD/j1'$YF\(AЃ]xiZk$5U܈?ZN:5ZC'Zܤ}w~HEVN'O:R|J%ءC.^ڎ`g͐(3!a [0ɘ»#c]j)`rsJ!*jcf`o+ ;mxx 2= }JKo a XN-K;xL@@a,u]ϺU,Y;Ia˯%y\ #2"daE޵>P~?nŠv]wZY׬a)33t2T۷MN6=?Cݹސd}1y"9gV˚!Z1qz&Ww-fRC|K>'cwA?`6$,|Ckٝ0->\#˽5KLiTom\[کNJXu}ꕵۡx[@4u g@+"R.AST+8S3r P,qݕV^fbڝ]d|k xtQ ä=:qC/ѾK69@̦8ۃ)6mkϋz{vC Gv̠d lCȇ`hr.SFmإ>2푈n\y 3k43b?sNjT%a)2}7 I }A6m"o'iLII5y?|Ue-Ңhb=Ϫ۱_*'{h3ry":U@>q|J!׎72ZΝ ]p%},r Tāeu1't̖Xm٩X$:Dl>OKX[;4Eh!BAjZ<|:f^Oh5a Ku/bztw~8i$oot^3Q?rLˊfoInHiqUgg)Ӈi-aui4,a{ nY$HkJcJ8@t1Ay8RQ)( qr<'T2QUET ԫ *DWV-J(YWZ~]^oP6{ [=ʤƔڗ>!C/9kyyrL+>;ʒ[/ fn>O< 1#ryw70"aYM0Ib8H^-ri a ޴B7N9!gI 2iOB *{Ȫ!&FsSmt*Vch|ʢ&E=E+BJ&Q"/qd"8Yn$:W|8a% F~\\ =w帙"i4}BW3߬[o4Yf"31Doڔr]CpϼAylk7S Lj @>s%0)uA 9-^{#x/ަL[`0/(?¨Y)؛a wI{ddC1ڐGdj<R0*eYCNsI(~.D*; ڻ{VqS[BOl]yWMRZ$.%qj"̙.9*H*:HfcEpRoQ#"htL\V Of}=Q]LH|_~kϣ񏈔vrți&!*)rIb@쪖%M5Нs!N=3h%`U3yV| pk,6խ]+{EΗ\^yn۔.*QzMOտD'TS\0WU'5:#h΅A%EZʜ5bҜ6M.^qӶX(1]l(4AҢۋVXkv)^ۚn6eQ~q`a4ElZ{!eٹ Rfmwš|Nwda{%Q cygRA9zXBN|5ّO49_w9.fo(D\EPl~PˢA'Ǐm |)]ˍ1<|`){y?J;|Ɠ=J7MMA~weHb^;+4T1纲ѳ'ZNWRfZx R}Eڢu^} =ּ3CAlC\'EΩ).b.-GB؄HA|ZEy˭yH: $'Xv3&yVQJ/I^ '4ZY[}>ēnѭţvTow(kxǂ Կ^gWzۼr1k }Pc.fŝL@^-7pjorͤDⶴ ppKtrU}$gmJtAPv h*ٲ͛-Zv&dHj|4P9?]]zw wLz zЩ!.+',zb8*߮$jΆ,7bCo/]Eh+#PN: q͸E@G4+5|"E@8xy>XqI3%4&Ueѣxޜ+V[ W?$U7H2ܘm &{}3}`RU=}ii*"Q:, !86ܤP'TsrvwMDKOxinM'\W mFfPOV \`%~JJvCm8kv9EgfvG١w20$-\IMD7OۺrU :Qڃ1<; -:z^%qBZKQD{җxoe%*p 7|-t<^xأbT*n }ۙo˞(ﴲ\^(Zn3fZ,2:"n@{8,-^wQRE~ '>@^U>W5 %3#X5"߶縵mw #,,C8閅WO=ĻH7=ζ:+ ᓞ(NQxTa7$m};aÿmk.47Kt݋B{Z=+IwoN.R"kO5haCK0OP$/{qu[_f_".wy$8)"oX;34Z'G&o5gȬ [푂px$~VlYy?A:O0O.?Iv{~ lz]%xդ1G2 ͯ4` 1w^"B~<׎kh:&9Dɗ@ I4|ߖ^y~r׮ۙ|,y-nQߖBN"n%;TsB֭f =3EXX7W s i*(*+"AC.ڥ+:WR^mSQMz+ . sS!F]bZxL}NN $pgvE mA~DPh#.0k㲧on?֭l/Ox$] L`.\(P+:rj{x}cO#V ̥): f(ýQ ǀ*[յ~-`h1):ҙn@-݁'>c(>,U0.Q/sU*kޑR1&&;{=< QdÅR%R F@"zEG1M}<*:Q5 zW ՟DKj~_  [#Z/9XMFۇ{7șک+hsDf!!/y {ܸ=g0<)84TMʦzj^K"$L+܏!^\*d%\%Ns$Z:˼&,t 'U}~# \ɝ/!-mYVB-Ei8ɷ92jW][тQT~79E3SѧB0n+\q\Xh;edIx6> XCVrpNFK|99QPba-~ $GnX?:a.pf. !®Cf߄Z$ ݞ\؉jrvb1F4 %B B k"r,$$\7K5sn_ +v P$ϩ3/x>Jaw/TiXFN)@ԅAK$r>Gnc QR] ]e\C w^ʺ𑞯W6ު}LB|ұ61R pn=  b>@kDRƌB MQnh50qb9j C_~Poaʀ1>bשiv63u_;fj/1'y9D8a n+.Zfq>ZTΟάs6 wV @)w1`h |ZwUia{]"5 X MDXfl|6b3Z=cddž/bWOgL  Á^ ~Їo;Lx0e_Z,Cõݷ%"({>96?C`/}G(? Zi 6m v{L3Z[ax'96!12'pͥ[˔))L@ƙV~+r2ʑkk9Z 0NG25raQJ #+Z,OhO :X=`O0 ߋWݴcZBb4l’ٟsԳܻYj(J՜:qZo%9" ]c,:ZrPA<@p/" g][uoW(AǸ3aIL/)^j_s;_"KY mĄ"oj=1HfΤ;F U\V>{9Yc6J?x̀W0M-7ؙHrV2 I<( 5uywjBtA֏o\e3YL\ʺkl#ss˯Gb/kBZ0rDhDq9WzC8 @C4.7U{_\_}#!|z(12Od@C?x7 N.?yjvGCҌ"ʚYlC`2'%b[iܫ6hLF HO] M"U1P [9X |UB S~z|.4TP{.b9py-~^z \@JX`nbDWpk9_c,:2YaFμҦ׭b1DLcau"ҝTT 7+ovzӀƣ iO~}$f}e]Է99y26WLuS Mvq9t)iG׉06G -0I#u1}ŭ[cz6WŁ!-pi?K8'`PCrrp\B;ki~8߯I{'DʪJ"am@!BS҂ ?{łk}MqWW,/R+OC[Yw3|ck=} Qc;Y4ed6nگlc`,ɩߤ@7iM=Gs4g%rGpHC5p#S/ڝ* ϓ]6}NxErP?SrbO{Qph*LbY Sn /BZ; }m~9a4-h[ ͎ϭJ$1N&|'c䬥/ʺ&᧥,/94 g)^D/P"܈Edӽ &S#pKDD Ț M9B4Ge@f~޻;a~WOk CL T|;v)␳aH z=lyNS^xG0fx!eƸ.9\( (noAiO@ut:)SPU6&*Bvp F~[@]Ja0dTx͊ZС q0.W2v1hd-CZVA@Gñ|g;=E4'K<@|4^q |\V1p%[#S#F#-CI̥+\),Wyy:#sQP^,JzF "穼ƹ0-hq(B?Z{)6{oݔ2WCtˋg5T8,+Oe0HUܺvRrAD 6ř!D)n:nc a=2ݫws9OYV@^XI{+ #bWy+@% 0.{'~{dzr/ێlL*bd_Ecfa"sص- v$95]&,̋PLY$8>=[w<* C~$\YY7W$Y^qF%EAWQ7{EH2C)Cu͔.w9AYȓKcd Ị< wTPNwbԡ"~H66_0wnDKAANe9iFVg?#|ּ^2|Ś{A&X|[QhY^oG|#W*fe`-ޣ\6i˺.tu/^ykA/˙5nnמz]1Z[ϝomV95˅_6 e^^!MMHчVx]m$ՏKJM4F-oQC23q/T])<6.jxo/|CA^[cB2|A {o1K{2A`O F8;' 9ƀ@bR]ʷq,Vo<*l^ܫQcT_5?$U0_9׊ f)Cץ) יP["q,6 #acd$\ـݻgyZgvbԷaz8{ț}BhA{mD.'*KOik;D #/h;@± !+ګ-ckn.v$?:ܗb{azKޣdGkyVֶZͥ:'Zsg.O\/+i.5j>( =>v w=7\4߈y~)qNKss~9< k {doÞ;Z荄AR4vríḾѲʀ&_>p9UF(#eI|K!Вl036nLGe*6Ne /ˌԎŪjj՚we7r|т֔讞 AZSCr ֔BInt~-#ZVvLBr"9ŗ598Vxh_d^:|xmW(~ My+)#%ʂu~ޯщ*KX8[4XL{J.. 5|E^]sҝcC~L@!=Iuzmʐ^IU:d݌a?a2h/iy;nQo (& =X;-?vkC) fm9ҟEf^-MזJ=4o,q˒i^X\lX޳ۓ{-:V{??&*_i]Ţ@T~9{UpMXאjS雩W::@VVپ=-}_ey{Ď^gifhjrԮ 0(w90{T,OT<~ >ϷXVX8^tΪ/y F&$ZLȏ!DHn˃8mL:dJ'!c\?<ƶ}@}݁ "'||2_}W 3:}6)X.邈Iemś[:ޝrmL#hd c^o;6a!mLS >nN-j'9BPB"7%"J<Z) }B [Sgԓd%7 O MmfZdQ?8k 8VjW{z 5zՄff2!]J73Cƅ2P,Mwǹ*)5H% s9ҏtIT H'~icK"~X=~KH^!Oq& "^S9c*l`t122Qd @Z1N[ :H\t܆CeSSR|DXECydhp9@<(+$̙4;.9댋)5des׷z$Uf{<&v$b)K WTR8Yj'?K^GW{o%8dwJgMz 3.7S[^n?ԣlC9XdC?5{/{/{ 2D{D uwo̧ CjcT#Ț y+L@w1c@]?|K 9dXe,r755뼼ِ\\5A 7 [B~bs^wE)`sOrя)eަlCZ@Kgߝz/miM)|DRѿ=/|pzWPC !Uqu.fc^tX\ZZJ9V]бو+|fq,ҏA_/儘(# :ΓkQn~C <ϳMfɥ$<;eڤ1%iEUgq*;R1=XhW`VUr7.Y"qyW(M&qψb)cAnjIW4ytҝ1Q܃j 6W!hd77"N˴:CM\ti1r[?Ѓo{TEzr 6k?ZQ[7/V{.=ծ"+9= KLe,`S w9oW͡ɓl _G׆aR0e_ǁu5X2k>[:kї/7:YÒ+W.1Ade;f4Y.H:^θ`"7%1$E5:DkP2r@5ݕ+Zf}G 7R=4GObT˷ ώ#_w Taҳjt[H -ysGdhAu.Z54N^RӲG2Qё\I>]zP=>';r?8Dx[k5j4ITU W0*hڬFgLRgX,cA!*}% sY|{F+u]$_oIr+sźv8sR?,%_'N,8+ kħFgd/$[5'Zǡ)A{P {2dfܥC(QUg1r\;Hbb τe+lI""Ӝ .?>ikV2Yr.6ы<OF}Klc+$#˧{ɘ 6S9Ґud`*ٕX5=eou7~4-xf&|ۼc;¼,Z_ݥ&k㯩 \&cwFc렮7ؔWK]}QY:H A=r/KuWT7Voi;Ս+ݖO?em+9W*3Mu=-ZR)Qv!EQa(9P+Bv{@E5*q]?vS!W㐸7g!N£IrWOԇdmbWBM!*I>t39 3D˓ʬy*{+ IfD$5w[EGeLeurH1T~ΧtWyw$vsjf2(dFg]kSz!~']:4`lyi1Yʸ7yT)IJu ^ճķ'^DvIwN{+$>| ؿzFda ObDL{̬o<5|ʐ-DIߚkyBoW+o^'^N? =8\|7rp0~Iq X3 Xdyzl0E p)KdBĔ,DK Ξkm?^$ fRd9M"Q%ƨѣfHç]9_RUAq}<=^F-ڋV욽Vq*ĝ/sru!`D[Iw=) EkvkȿgouS,`*糣: g mb|{{qOuyeڬ(+7oʈz0'#2VQǗME} LK4~I:ֲnj5'Je9wse>{hPg,f!k土^Ɔl|wu|Ñ߬DQx3Ckp)eC>Ԟ$2f=:Hh5ڢhFL,@:E~7BV?Q#3QA.јڬxWujTa7`N"*kKbYJD: ,T3sq%̓!Lo oPMZ~8_BUh2|H@mEj]<m wFɇ|![$Q#zT֞N6 讎HNb!b'rV!Rn&>ww)rR`><\|a +Q۹o=b$Jhܒ"A丄uu?\hG!7˽&K>p50E*~#>ĤR>p8%q{}# pqͿfOG[pVarNv @`HrrUHkέ|zg,tQͭNb)Y0G}ws=?1]Ο.:X ӻ$Vލځsw/@@{W,}v✥"ԸzEIIKUŏIeP`fq4ꒀy]%] -"Փ9szRi ٪Ӎ럤1!Sj3 ^-S`Y9%̥ʒ>2.-}pѷ7^-R2U[KV^j]N牅a"}-| k2a^!b)-D*57hoѠJ?\ζn<oQ0^06%g>)fU*7U'M$+6_7 ԤY|jipUzǵA[ .`{ f"[ꨃH170u eeɲHk.a03eTuu+(l:*owQʑGwE8wU՛nK- ͎KMr9]ay+2p+ҹx?_Q{(Ƕ; -!1FR9nf !К?n cD$=Kn,PYgxqͩ'C }G%3CgQӜc$n%lcfUˌN^ޤM-'KVϚ9yezbQȵƏxTRQ5~ ^u9g3f {&#TuH8%2t):N#s??%?05љT*Rg)Sאy"҇SAܻ錪)qRK=WH=.(<>L},7汫ƎP s+fIX\h;sb).VĦ,|pUYY }0ӐTzqMeRp -NS\ .] HdvidK9}dqzK5nX e5bF6ʍmC@;?{R,l=pe(FM-c<: GНn喊&RaRVz*/ҴT#H6v#I(V!QҠG߄+xm2k3zU35հ2o~Gqrv * [ՒC[~:m&$4ijB84|؍pHr+ƺQ)؂I gHSba-ui-l/о0\M}K?FdD{={<ԍ^Ѡ;|x݋ ]94jFaf|l\Q!r53Lc6?aa5cG|-ls^8%6uO9Qǟ nXIx4paܽfζK~?+2yIb);(JΕFH+*1&"ɰɍPa%'of?cOOK 8VzMécg֧6Y_} om+zgT|VQ?'"xR;gO^L8;qaߘlLbL\Ww>k~[gwk:>2}ZB{W ,w&S ka@Ը?6>3n=)?{2H2, )qH` ޕ3jkTĞB?Qm$%)}bUq_cqY -_1Ӂ)j?E=7>-96l. sx"hc[y7?N - TK79|ѰxzjgmhInHog)v~ C;LJqu pmW<˗=l+(lCPm-[IHHK(|LQkgª?CEBx}QN";FNUcE\k5EG н^Jv<+Dk rKCN w¹*{Ϛ>jhÉW~{|kÿ$a=g1izf҆Mm  z`0X*+Gn ?J >[Std>)`zdM+9,Z', į>cu}nmĐN=z8$Rգ3c 1MEKY$ 5 ]Y^=xܠKHUNyxUqYd*ggmnL%r䰼!@Z"["(͘pfk"v$ρ9&LIQV:WIZk7TT!X52QIe(ZP b}LLϰ:.'T/ kS->lT5}Tr#e(SG:'WmP 8oVV7S*6⋫-7kI5P|-wSX-g `(TzI(jaZc^w.8g-fV]hl3.yOu2&8EAD|L|Z3ɡ2]ۑ5KqO[شܵ,Մ>k*jsέ *Ѯ|\A[ T O=5@'z=]Z(CGEfM8GWP+qNEmF068Z:b7-Ь%{Ch1^tm,R\H TZ#x㮽`Y'}?}iou8KP1㥙夆CZ"8@x µ-``Pj}6LlRU\6[ CZN"*Y=3CȾ3ڣx~,ceG ;,5R>Uw6ԼSAR7|aqu^ځ;V`ۼ:{~۔x9:7N+m1f75dGrzZFݬ(:%P 9GaxLIrl2}>Mn?KwE/:T@Y_a^OME^3 O\s _ ^9$-Q5y'msс cvV I߇!?I$7ܡ\ód[#mH܁F&8$*pw,意hiḩt-,6i0I^,`Ś7{~5QR ]5j^FiT\?8E|ӕ_eoH{UĠT&L-3QWnԤuM* ۥD+%j;bͮ' Y> (؟4 w]|/JW#ȤZca7B'8:{} N$8oQ|W mOnL)Q^!WCM8}:Nhۑc&4ٝqo_@xމɐ5 Q+t*\]w C!W^"ywne/R=`*5bJzMwZN h PQ7޴-␜EgC29*XYKUk&D\4]aw-5&_kD@;I1fͫ{C[ŏY}ExdS9ɇ@~$`KPK}=wvZR ?Ph{%Zdϙ'biys-KhOü. [4/%0y]|(珫DBˀ(D뺹"cfw8NgPmzdo *Ģj6hni[}iY LٱEf9eF8dǣOk@p#B\'Mo=) uĐEB>:6Qlo6]Z* ) ˸kֿ /d?6 Q7Dx'ey:KCaM۽T&ufTx_WD){5PJ7A 2wWqo-Cg*te j ^"~4{;fo-W?*wW1{|k.QZ" X-J/~ ۵dp;} WAD|Qķ~XC}6cT;k#7.{7c8T_4X;B*bm#"""*RJ)EDDDD̛?97t3Zkgсhzt&ޯw.YNˋվgH@E!6~brݴz]DDDDDDDfffffffVUUUUUUUi{z6Nddoit-0.36.0/doc/_static/vendor/owl.carousel/000077500000000000000000000000001423054503100206415ustar00rootroot00000000000000doit-0.36.0/doc/_static/vendor/owl.carousel/owl.carousel.css000066400000000000000000000100201423054503100237610ustar00rootroot00000000000000/** * Owl Carousel v2.2.0 * Copyright 2013-2016 David Deutsch * Licensed under MIT (https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE) */ /* * Owl Carousel - Core */ .owl-carousel { display: none; width: 100%; -webkit-tap-highlight-color: transparent; /* position relative and z-index fix webkit rendering fonts issue */ position: relative; z-index: 1; } .owl-carousel .owl-stage { position: relative; -ms-touch-action: pan-Y; } .owl-carousel .owl-stage:after { content: "."; display: block; clear: both; visibility: hidden; line-height: 0; height: 0; } .owl-carousel .owl-stage-outer { position: relative; overflow: hidden; /* fix for flashing background */ -webkit-transform: translate3d(0px, 0px, 0px); } .owl-carousel .owl-item { position: relative; min-height: 1px; float: left; -webkit-backface-visibility: hidden; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; } .owl-carousel .owl-item img { display: block; width: 100%; -webkit-transform-style: preserve-3d; } .owl-carousel .owl-nav.disabled, .owl-carousel .owl-dots.disabled { display: none; } .owl-carousel .owl-nav .owl-prev, .owl-carousel .owl-nav .owl-next, .owl-carousel .owl-dot { cursor: pointer; cursor: hand; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .owl-carousel.owl-loaded { display: block; } .owl-carousel.owl-loading { opacity: 0; display: block; } .owl-carousel.owl-hidden { opacity: 0; } .owl-carousel.owl-refresh .owl-item { visibility: hidden; } .owl-carousel.owl-drag .owl-item { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .owl-carousel.owl-grab { cursor: move; cursor: grab; } .owl-carousel.owl-rtl { direction: rtl; } .owl-carousel.owl-rtl .owl-item { float: right; } /* No Js */ .no-js .owl-carousel { display: block; } /* * Owl Carousel - Animate Plugin */ .owl-carousel .animated { -webkit-animation-duration: 1000ms; animation-duration: 1000ms; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .owl-carousel .owl-animated-in { z-index: 0; } .owl-carousel .owl-animated-out { z-index: 1; } .owl-carousel .fadeOut { -webkit-animation-name: fadeOut; animation-name: fadeOut; } @-webkit-keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } /* * Owl Carousel - Auto Height Plugin */ .owl-height { transition: height 500ms ease-in-out; } /* * Owl Carousel - Lazy Load Plugin */ .owl-carousel .owl-item .owl-lazy { opacity: 0; transition: opacity 400ms ease; } .owl-carousel .owl-item img.owl-lazy { -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } /* * Owl Carousel - Video Plugin */ .owl-carousel .owl-video-wrapper { position: relative; height: 100%; background: #000; } .owl-carousel .owl-video-play-icon { position: absolute; height: 80px; width: 80px; left: 50%; top: 50%; margin-left: -40px; margin-top: -40px; background: url("owl.video.play.png") no-repeat; cursor: pointer; z-index: 1; -webkit-backface-visibility: hidden; transition: -webkit-transform 100ms ease; transition: transform 100ms ease; } .owl-carousel .owl-video-play-icon:hover { -webkit-transform: scale(1.3, 1.3); -ms-transform: scale(1.3, 1.3); transform: scale(1.3, 1.3); } .owl-carousel .owl-video-playing .owl-video-tn, .owl-carousel .owl-video-playing .owl-video-play-icon { display: none; } .owl-carousel .owl-video-tn { opacity: 0; height: 100%; background-position: center center; background-repeat: no-repeat; background-size: contain; transition: opacity 400ms ease; } .owl-carousel .owl-video-frame { position: relative; z-index: 1; height: 100%; width: 100%; } doit-0.36.0/doc/_static/vendor/owl.carousel/owl.carousel.min.js000066400000000000000000001235461423054503100244110ustar00rootroot00000000000000/** * Owl Carousel v2.2.0 * Copyright 2013-2016 David Deutsch * Licensed under MIT (https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE) */ !function(a,b,c,d){function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c),this.$element=a(b),this._handlers={},this._plugins={},this._supress={},this._current=null,this._speed=null,this._coordinates=[],this._breakpoint=null,this._width=null,this._items=[],this._clones=[],this._mergers=[],this._widths=[],this._invalidated={},this._pipe=[],this._drag={time:null,target:null,pointer:null,stage:{start:null,current:null},direction:null},this._states={current:{},tags:{initializing:["busy"],animating:["busy"],dragging:["interacting"]}},a.each(["onResize","onThrottledResize"],a.proxy(function(b,c){this._handlers[c]=a.proxy(this[c],this)},this)),a.each(e.Plugins,a.proxy(function(a,b){this._plugins[a.charAt(0).toLowerCase()+a.slice(1)]=new b(this)},this)),a.each(e.Workers,a.proxy(function(b,c){this._pipe.push({filter:c.filter,run:a.proxy(c.run,this)})},this)),this.setup(),this.initialize()}e.Defaults={items:3,loop:!1,center:!1,rewind:!1,mouseDrag:!0,touchDrag:!0,pullDrag:!0,freeDrag:!1,margin:0,stagePadding:0,merge:!1,mergeFit:!0,autoWidth:!1,startPosition:0,rtl:!1,smartSpeed:250,fluidSpeed:!1,dragEndSpeed:!1,responsive:{},responsiveRefreshRate:200,responsiveBaseElement:b,fallbackEasing:"swing",info:!1,nestedItemSelector:!1,itemElement:"div",stageElement:"div",refreshClass:"owl-refresh",loadedClass:"owl-loaded",loadingClass:"owl-loading",rtlClass:"owl-rtl",responsiveClass:"owl-responsive",dragClass:"owl-drag",itemClass:"owl-item",stageClass:"owl-stage",stageOuterClass:"owl-stage-outer",grabClass:"owl-grab"},e.Width={Default:"default",Inner:"inner",Outer:"outer"},e.Type={Event:"event",State:"state"},e.Plugins={},e.Workers=[{filter:["width","settings"],run:function(){this._width=this.$element.width()}},{filter:["width","items","settings"],run:function(a){a.current=this._items&&this._items[this.relative(this._current)]}},{filter:["items","settings"],run:function(){this.$stage.children(".cloned").remove()}},{filter:["width","items","settings"],run:function(a){var b=this.settings.margin||"",c=!this.settings.autoWidth,d=this.settings.rtl,e={width:"auto","margin-left":d?b:"","margin-right":d?"":b};!c&&this.$stage.children().css(e),a.css=e}},{filter:["width","items","settings"],run:function(a){var b=(this.width()/this.settings.items).toFixed(3)-this.settings.margin,c=null,d=this._items.length,e=!this.settings.autoWidth,f=[];for(a.items={merge:!1,width:b};d--;)c=this._mergers[d],c=this.settings.mergeFit&&Math.min(c,this.settings.items)||c,a.items.merge=c>1||a.items.merge,f[d]=e?b*c:this._items[d].width();this._widths=f}},{filter:["items","settings"],run:function(){var b=[],c=this._items,d=this.settings,e=Math.max(2*d.items,4),f=2*Math.ceil(c.length/2),g=d.loop&&c.length?d.rewind?e:Math.max(e,f):0,h="",i="";for(g/=2;g--;)b.push(this.normalize(b.length/2,!0)),h+=c[b[b.length-1]][0].outerHTML,b.push(this.normalize(c.length-1-(b.length-1)/2,!0)),i=c[b[b.length-1]][0].outerHTML+i;this._clones=b,a(h).addClass("cloned").appendTo(this.$stage),a(i).addClass("cloned").prependTo(this.$stage)}},{filter:["width","items","settings"],run:function(){for(var a=this.settings.rtl?1:-1,b=this._clones.length+this._items.length,c=-1,d=0,e=0,f=[];++cc;c++)a=this._coordinates[c-1]||0,b=Math.abs(this._coordinates[c])+f*e,(this.op(a,"<=",g)&&this.op(a,">",h)||this.op(b,"<",g)&&this.op(b,">",h))&&i.push(c);this.$stage.children(".active").removeClass("active"),this.$stage.children(":eq("+i.join("), :eq(")+")").addClass("active"),this.settings.center&&(this.$stage.children(".center").removeClass("center"),this.$stage.children().eq(this.current()).addClass("center"))}}],e.prototype.initialize=function(){if(this.enter("initializing"),this.trigger("initialize"),this.$element.toggleClass(this.settings.rtlClass,this.settings.rtl),this.settings.autoWidth&&!this.is("pre-loading")){var b,c,e;b=this.$element.find("img"),c=this.settings.nestedItemSelector?"."+this.settings.nestedItemSelector:d,e=this.$element.children(c).width(),b.length&&0>=e&&this.preloadAutoWidthImages(b)}this.$element.addClass(this.options.loadingClass),this.$stage=a("<"+this.settings.stageElement+' class="'+this.settings.stageClass+'"/>').wrap('
'),this.$element.append(this.$stage.parent()),this.replace(this.$element.children().not(this.$stage.parent())),this.$element.is(":visible")?this.refresh():this.invalidate("width"),this.$element.removeClass(this.options.loadingClass).addClass(this.options.loadedClass),this.registerEventHandlers(),this.leave("initializing"),this.trigger("initialized")},e.prototype.setup=function(){var b=this.viewport(),c=this.options.responsive,d=-1,e=null;c?(a.each(c,function(a){b>=a&&a>d&&(d=Number(a))}),e=a.extend({},this.options,c[d]),"function"==typeof e.stagePadding&&(e.stagePadding=e.stagePadding()),delete e.responsive,e.responsiveClass&&this.$element.attr("class",this.$element.attr("class").replace(new RegExp("("+this.options.responsiveClass+"-)\\S+\\s","g"),"$1"+d))):e=a.extend({},this.options),this.trigger("change",{property:{name:"settings",value:e}}),this._breakpoint=d,this.settings=e,this.invalidate("settings"),this.trigger("changed",{property:{name:"settings",value:this.settings}})},e.prototype.optionsLogic=function(){this.settings.autoWidth&&(this.settings.stagePadding=!1,this.settings.merge=!1)},e.prototype.prepare=function(b){var c=this.trigger("prepare",{content:b});return c.data||(c.data=a("<"+this.settings.itemElement+"/>").addClass(this.options.itemClass).append(b)),this.trigger("prepared",{content:c.data}),c.data},e.prototype.update=function(){for(var b=0,c=this._pipe.length,d=a.proxy(function(a){return this[a]},this._invalidated),e={};c>b;)(this._invalidated.all||a.grep(this._pipe[b].filter,d).length>0)&&this._pipe[b].run(e),b++;this._invalidated={},!this.is("valid")&&this.enter("valid")},e.prototype.width=function(a){switch(a=a||e.Width.Default){case e.Width.Inner:case e.Width.Outer:return this._width;default:return this._width-2*this.settings.stagePadding+this.settings.margin}},e.prototype.refresh=function(){this.enter("refreshing"),this.trigger("refresh"),this.setup(),this.optionsLogic(),this.$element.addClass(this.options.refreshClass),this.update(),this.$element.removeClass(this.options.refreshClass),this.leave("refreshing"),this.trigger("refreshed")},e.prototype.onThrottledResize=function(){b.clearTimeout(this.resizeTimer),this.resizeTimer=b.setTimeout(this._handlers.onResize,this.settings.responsiveRefreshRate)},e.prototype.onResize=function(){return this._items.length?this._width===this.$element.width()?!1:this.$element.is(":visible")?(this.enter("resizing"),this.trigger("resize").isDefaultPrevented()?(this.leave("resizing"),!1):(this.invalidate("width"),this.refresh(),this.leave("resizing"),void this.trigger("resized"))):!1:!1},e.prototype.registerEventHandlers=function(){a.support.transition&&this.$stage.on(a.support.transition.end+".owl.core",a.proxy(this.onTransitionEnd,this)),this.settings.responsive!==!1&&this.on(b,"resize",this._handlers.onThrottledResize),this.settings.mouseDrag&&(this.$element.addClass(this.options.dragClass),this.$stage.on("mousedown.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("dragstart.owl.core selectstart.owl.core",function(){return!1})),this.settings.touchDrag&&(this.$stage.on("touchstart.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("touchcancel.owl.core",a.proxy(this.onDragEnd,this)))},e.prototype.onDragStart=function(b){var d=null;3!==b.which&&(a.support.transform?(d=this.$stage.css("transform").replace(/.*\(|\)| /g,"").split(","),d={x:d[16===d.length?12:4],y:d[16===d.length?13:5]}):(d=this.$stage.position(),d={x:this.settings.rtl?d.left+this.$stage.width()-this.width()+this.settings.margin:d.left,y:d.top}),this.is("animating")&&(a.support.transform?this.animate(d.x):this.$stage.stop(),this.invalidate("position")),this.$element.toggleClass(this.options.grabClass,"mousedown"===b.type),this.speed(0),this._drag.time=(new Date).getTime(),this._drag.target=a(b.target),this._drag.stage.start=d,this._drag.stage.current=d,this._drag.pointer=this.pointer(b),a(c).on("mouseup.owl.core touchend.owl.core",a.proxy(this.onDragEnd,this)),a(c).one("mousemove.owl.core touchmove.owl.core",a.proxy(function(b){var d=this.difference(this._drag.pointer,this.pointer(b));a(c).on("mousemove.owl.core touchmove.owl.core",a.proxy(this.onDragMove,this)),Math.abs(d.x)0^this.settings.rtl?"left":"right";a(c).off(".owl.core"),this.$element.removeClass(this.options.grabClass),(0!==d.x&&this.is("dragging")||!this.is("valid"))&&(this.speed(this.settings.dragEndSpeed||this.settings.smartSpeed),this.current(this.closest(e.x,0!==d.x?f:this._drag.direction)),this.invalidate("position"),this.update(),this._drag.direction=f,(Math.abs(d.x)>3||(new Date).getTime()-this._drag.time>300)&&this._drag.target.one("click.owl.core",function(){return!1})),this.is("dragging")&&(this.leave("dragging"),this.trigger("dragged"))},e.prototype.closest=function(b,c){var d=-1,e=30,f=this.width(),g=this.coordinates();return this.settings.freeDrag||a.each(g,a.proxy(function(a,h){return"left"===c&&b>h-e&&h+e>b?d=a:"right"===c&&b>h-f-e&&h-f+e>b?d=a+1:this.op(b,"<",h)&&this.op(b,">",g[a+1]||h-f)&&(d="left"===c?a+1:a),-1===d},this)),this.settings.loop||(this.op(b,">",g[this.minimum()])?d=b=this.minimum():this.op(b,"<",g[this.maximum()])&&(d=b=this.maximum())),d},e.prototype.animate=function(b){var c=this.speed()>0;this.is("animating")&&this.onTransitionEnd(),c&&(this.enter("animating"),this.trigger("translate")),a.support.transform3d&&a.support.transition?this.$stage.css({transform:"translate3d("+b+"px,0px,0px)",transition:this.speed()/1e3+"s"}):c?this.$stage.animate({left:b+"px"},this.speed(),this.settings.fallbackEasing,a.proxy(this.onTransitionEnd,this)):this.$stage.css({left:b+"px"})},e.prototype.is=function(a){return this._states.current[a]&&this._states.current[a]>0},e.prototype.current=function(a){if(a===d)return this._current;if(0===this._items.length)return d;if(a=this.normalize(a),this._current!==a){var b=this.trigger("change",{property:{name:"position",value:a}});b.data!==d&&(a=this.normalize(b.data)),this._current=a,this.invalidate("position"),this.trigger("changed",{property:{name:"position",value:this._current}})}return this._current},e.prototype.invalidate=function(b){return"string"===a.type(b)&&(this._invalidated[b]=!0,this.is("valid")&&this.leave("valid")),a.map(this._invalidated,function(a,b){return b})},e.prototype.reset=function(a){a=this.normalize(a),a!==d&&(this._speed=0,this._current=a,this.suppress(["translate","translated"]),this.animate(this.coordinates(a)),this.release(["translate","translated"]))},e.prototype.normalize=function(a,b){var c=this._items.length,e=b?0:this._clones.length;return!this.isNumeric(a)||1>c?a=d:(0>a||a>=c+e)&&(a=((a-e/2)%c+c)%c+e/2),a},e.prototype.relative=function(a){return a-=this._clones.length/2,this.normalize(a,!0)},e.prototype.maximum=function(a){var b,c,d,e=this.settings,f=this._coordinates.length;if(e.loop)f=this._clones.length/2+this._items.length-1;else if(e.autoWidth||e.merge){for(b=this._items.length,c=this._items[--b].width(),d=this.$element.width();b--&&(c+=this._items[b].width()+this.settings.margin,!(c>d)););f=b+1}else f=e.center?this._items.length-1:this._items.length-e.items;return a&&(f-=this._clones.length/2),Math.max(f,0)},e.prototype.minimum=function(a){return a?0:this._clones.length/2},e.prototype.items=function(a){return a===d?this._items.slice():(a=this.normalize(a,!0),this._items[a])},e.prototype.mergers=function(a){return a===d?this._mergers.slice():(a=this.normalize(a,!0),this._mergers[a])},e.prototype.clones=function(b){var c=this._clones.length/2,e=c+this._items.length,f=function(a){return a%2===0?e+a/2:c-(a+1)/2};return b===d?a.map(this._clones,function(a,b){return f(b)}):a.map(this._clones,function(a,c){return a===b?f(c):null})},e.prototype.speed=function(a){return a!==d&&(this._speed=a),this._speed},e.prototype.coordinates=function(b){var c,e=1,f=b-1;return b===d?a.map(this._coordinates,a.proxy(function(a,b){return this.coordinates(b)},this)):(this.settings.center?(this.settings.rtl&&(e=-1,f=b+1),c=this._coordinates[b],c+=(this.width()-c+(this._coordinates[f]||0))/2*e):c=this._coordinates[f]||0,c=Math.ceil(c))},e.prototype.duration=function(a,b,c){return 0===c?0:Math.min(Math.max(Math.abs(b-a),1),6)*Math.abs(c||this.settings.smartSpeed)},e.prototype.to=function(a,b){var c=this.current(),d=null,e=a-this.relative(c),f=(e>0)-(0>e),g=this._items.length,h=this.minimum(),i=this.maximum();this.settings.loop?(!this.settings.rewind&&Math.abs(e)>g/2&&(e+=-1*f*g),a=c+e,d=((a-h)%g+g)%g+h,d!==a&&i>=d-e&&d-e>0&&(c=d-e,a=d,this.reset(c))):this.settings.rewind?(i+=1,a=(a%i+i)%i):a=Math.max(h,Math.min(i,a)),this.speed(this.duration(c,a,b)),this.current(a),this.$element.is(":visible")&&this.update()},e.prototype.next=function(a){a=a||!1,this.to(this.relative(this.current())+1,a)},e.prototype.prev=function(a){a=a||!1,this.to(this.relative(this.current())-1,a)},e.prototype.onTransitionEnd=function(a){return a!==d&&(a.stopPropagation(),(a.target||a.srcElement||a.originalTarget)!==this.$stage.get(0))?!1:(this.leave("animating"),void this.trigger("translated"))},e.prototype.viewport=function(){var d;if(this.options.responsiveBaseElement!==b)d=a(this.options.responsiveBaseElement).width();else if(b.innerWidth)d=b.innerWidth;else{if(!c.documentElement||!c.documentElement.clientWidth)throw"Can not detect viewport width.";d=c.documentElement.clientWidth}return d},e.prototype.replace=function(b){this.$stage.empty(),this._items=[],b&&(b=b instanceof jQuery?b:a(b)),this.settings.nestedItemSelector&&(b=b.find("."+this.settings.nestedItemSelector)),b.filter(function(){return 1===this.nodeType}).each(a.proxy(function(a,b){b=this.prepare(b),this.$stage.append(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)},this)),this.reset(this.isNumeric(this.settings.startPosition)?this.settings.startPosition:0),this.invalidate("items")},e.prototype.add=function(b,c){var e=this.relative(this._current);c=c===d?this._items.length:this.normalize(c,!0),b=b instanceof jQuery?b:a(b),this.trigger("add",{content:b,position:c}),b=this.prepare(b),0===this._items.length||c===this._items.length?(0===this._items.length&&this.$stage.append(b),0!==this._items.length&&this._items[c-1].after(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)):(this._items[c].before(b),this._items.splice(c,0,b),this._mergers.splice(c,0,1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)),this._items[e]&&this.reset(this._items[e].index()),this.invalidate("items"),this.trigger("added",{content:b,position:c})},e.prototype.remove=function(a){a=this.normalize(a,!0),a!==d&&(this.trigger("remove",{content:this._items[a],position:a}),this._items[a].remove(),this._items.splice(a,1),this._mergers.splice(a,1),this.invalidate("items"),this.trigger("removed",{content:null,position:a}))},e.prototype.preloadAutoWidthImages=function(b){b.each(a.proxy(function(b,c){this.enter("pre-loading"),c=a(c),a(new Image).one("load",a.proxy(function(a){c.attr("src",a.target.src),c.css("opacity",1),this.leave("pre-loading"),!this.is("pre-loading")&&!this.is("initializing")&&this.refresh()},this)).attr("src",c.attr("src")||c.attr("data-src")||c.attr("data-src-retina"))},this))},e.prototype.destroy=function(){this.$element.off(".owl.core"),this.$stage.off(".owl.core"),a(c).off(".owl.core"),this.settings.responsive!==!1&&(b.clearTimeout(this.resizeTimer),this.off(b,"resize",this._handlers.onThrottledResize));for(var d in this._plugins)this._plugins[d].destroy();this.$stage.children(".cloned").remove(),this.$stage.unwrap(),this.$stage.children().contents().unwrap(),this.$stage.children().unwrap(),this.$element.removeClass(this.options.refreshClass).removeClass(this.options.loadingClass).removeClass(this.options.loadedClass).removeClass(this.options.rtlClass).removeClass(this.options.dragClass).removeClass(this.options.grabClass).attr("class",this.$element.attr("class").replace(new RegExp(this.options.responsiveClass+"-\\S+\\s","g"),"")).removeData("owl.carousel")},e.prototype.op=function(a,b,c){var d=this.settings.rtl;switch(b){case"<":return d?a>c:c>a;case">":return d?c>a:a>c;case">=":return d?c>=a:a>=c;case"<=":return d?a>=c:c>=a}},e.prototype.on=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},e.prototype.off=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent&&a.detachEvent("on"+b,c)},e.prototype.trigger=function(b,c,d,f,g){var h={item:{count:this._items.length,index:this.current()}},i=a.camelCase(a.grep(["on",b,d],function(a){return a}).join("-").toLowerCase()),j=a.Event([b,"owl",d||"carousel"].join(".").toLowerCase(),a.extend({relatedTarget:this},h,c));return this._supress[b]||(a.each(this._plugins,function(a,b){b.onTrigger&&b.onTrigger(j)}),this.register({type:e.Type.Event,name:b}),this.$element.trigger(j),this.settings&&"function"==typeof this.settings[i]&&this.settings[i].call(this,j)),j},e.prototype.enter=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]===d&&(this._states.current[b]=0),this._states.current[b]++},this))},e.prototype.leave=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]--},this))},e.prototype.register=function(b){if(b.type===e.Type.Event){if(a.event.special[b.name]||(a.event.special[b.name]={}),!a.event.special[b.name].owl){var c=a.event.special[b.name]._default;a.event.special[b.name]._default=function(a){return!c||!c.apply||a.namespace&&-1!==a.namespace.indexOf("owl")?a.namespace&&a.namespace.indexOf("owl")>-1:c.apply(this,arguments)},a.event.special[b.name].owl=!0}}else b.type===e.Type.State&&(this._states.tags[b.name]?this._states.tags[b.name]=this._states.tags[b.name].concat(b.tags):this._states.tags[b.name]=b.tags,this._states.tags[b.name]=a.grep(this._states.tags[b.name],a.proxy(function(c,d){return a.inArray(c,this._states.tags[b.name])===d},this)))},e.prototype.suppress=function(b){a.each(b,a.proxy(function(a,b){this._supress[b]=!0},this))},e.prototype.release=function(b){a.each(b,a.proxy(function(a,b){delete this._supress[b]},this))},e.prototype.pointer=function(a){var c={x:null,y:null};return a=a.originalEvent||a||b.event,a=a.touches&&a.touches.length?a.touches[0]:a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:a,a.pageX?(c.x=a.pageX,c.y=a.pageY):(c.x=a.clientX,c.y=a.clientY),c},e.prototype.isNumeric=function(a){return!isNaN(parseFloat(a))},e.prototype.difference=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},a.fn.owlCarousel=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),f=d.data("owl.carousel");f||(f=new e(this,"object"==typeof b&&b),d.data("owl.carousel",f),a.each(["next","prev","to","destroy","refresh","replace","add","remove"],function(b,c){f.register({type:e.Type.Event,name:c}),f.$element.on(c+".owl.carousel.core",a.proxy(function(a){a.namespace&&a.relatedTarget!==this&&(this.suppress([c]),f[c].apply(this,[].slice.call(arguments,1)),this.release([c]))},f))})),"string"==typeof b&&"_"!==b.charAt(0)&&f[b].apply(f,c)})},a.fn.owlCarousel.Constructor=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._visible=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoRefresh&&this.watch()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoRefresh:!0,autoRefreshInterval:500},e.prototype.watch=function(){this._interval||(this._visible=this._core.$element.is(":visible"),this._interval=b.setInterval(a.proxy(this.refresh,this),this._core.settings.autoRefreshInterval))},e.prototype.refresh=function(){this._core.$element.is(":visible")!==this._visible&&(this._visible=!this._visible,this._core.$element.toggleClass("owl-hidden",!this._visible),this._visible&&this._core.invalidate("width")&&this._core.refresh())},e.prototype.destroy=function(){var a,c;b.clearInterval(this._interval);for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoRefresh=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._loaded=[],this._handlers={"initialized.owl.carousel change.owl.carousel resized.owl.carousel":a.proxy(function(b){if(b.namespace&&this._core.settings&&this._core.settings.lazyLoad&&(b.property&&"position"==b.property.name||"initialized"==b.type))for(var c=this._core.settings,e=c.center&&Math.ceil(c.items/2)||c.items,f=c.center&&-1*e||0,g=(b.property&&b.property.value!==d?b.property.value:this._core.current())+f,h=this._core.clones().length,i=a.proxy(function(a,b){this.load(b)},this);f++-1||(e.each(a.proxy(function(c,d){var e,f=a(d),g=b.devicePixelRatio>1&&f.attr("data-src-retina")||f.attr("data-src");this._core.trigger("load",{element:f,url:g},"lazy"),f.is("img")?f.one("load.owl.lazy",a.proxy(function(){f.css("opacity",1),this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("src",g):(e=new Image,e.onload=a.proxy(function(){f.css({"background-image":"url("+g+")",opacity:"1"}),this._core.trigger("loaded",{element:f,url:g},"lazy")},this),e.src=g)},this)),this._loaded.push(d.get(0)))},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this._core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Lazy=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._handlers={"initialized.owl.carousel refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&this.update()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&"position"==a.property.name&&this.update()},this),"loaded.owl.lazy":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&a.element.closest("."+this._core.settings.itemClass).index()===this._core.current()&&this.update()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoHeight:!1,autoHeightClass:"owl-height"},e.prototype.update=function(){var b=this._core._current,c=b+this._core.settings.items,d=this._core.$stage.children().toArray().slice(b,c),e=[],f=0;a.each(d,function(b,c){e.push(a(c).height())}),f=Math.max.apply(null,e),this._core.$stage.parent().height(f).addClass(this._core.settings.autoHeightClass)},e.prototype.destroy=function(){var a,b;for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoHeight=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._videos={},this._playing=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.register({type:"state",name:"playing",tags:["interacting"]})},this),"resize.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.video&&this.isInFullScreen()&&a.preventDefault()},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.is("resizing")&&this._core.$stage.find(".cloned .owl-video-frame").remove()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"===a.property.name&&this._playing&&this.stop()},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find(".owl-video");c.length&&(c.css("display","none"),this.fetch(c,a(b.content)))}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._core.$element.on("click.owl.video",".owl-video-play-icon",a.proxy(function(a){this.play(a)},this))};e.Defaults={video:!1,videoHeight:!1,videoWidth:!1},e.prototype.fetch=function(a,b){var c=function(){return a.attr("data-vimeo-id")?"vimeo":a.attr("data-vzaar-id")?"vzaar":"youtube"}(),d=a.attr("data-vimeo-id")||a.attr("data-youtube-id")||a.attr("data-vzaar-id"),e=a.attr("data-width")||this._core.settings.videoWidth,f=a.attr("data-height")||this._core.settings.videoHeight,g=a.attr("href");if(!g)throw new Error("Missing video URL.");if(d=g.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/),d[3].indexOf("youtu")>-1)c="youtube";else if(d[3].indexOf("vimeo")>-1)c="vimeo";else{if(!(d[3].indexOf("vzaar")>-1))throw new Error("Video URL not supported.");c="vzaar"}d=d[6],this._videos[g]={type:c,id:d,width:e,height:f},b.attr("data-video",g),this.thumbnail(a,this._videos[g])},e.prototype.thumbnail=function(b,c){var d,e,f,g=c.width&&c.height?'style="width:'+c.width+"px;height:"+c.height+'px;"':"",h=b.find("img"),i="src",j="",k=this._core.settings,l=function(a){e='
',d=k.lazyLoad?'
':'
',b.after(d),b.after(e)};return b.wrap('
"),this._core.settings.lazyLoad&&(i="data-src",j="owl-lazy"),h.length?(l(h.attr(i)),h.remove(),!1):void("youtube"===c.type?(f="//img.youtube.com/vi/"+c.id+"/hqdefault.jpg",l(f)):"vimeo"===c.type?a.ajax({type:"GET",url:"//vimeo.com/api/v2/video/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a[0].thumbnail_large,l(f)}}):"vzaar"===c.type&&a.ajax({type:"GET",url:"//vzaar.com/api/videos/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a.framegrab_url,l(f)}}))},e.prototype.stop=function(){this._core.trigger("stop",null,"video"),this._playing.find(".owl-video-frame").remove(),this._playing.removeClass("owl-video-playing"),this._playing=null,this._core.leave("playing"),this._core.trigger("stopped",null,"video")},e.prototype.play=function(b){var c,d=a(b.target),e=d.closest("."+this._core.settings.itemClass),f=this._videos[e.attr("data-video")],g=f.width||"100%",h=f.height||this._core.$stage.height();this._playing||(this._core.enter("playing"),this._core.trigger("play",null,"video"),e=this._core.items(this._core.relative(e.index())),this._core.reset(e.index()),"youtube"===f.type?c='':"vimeo"===f.type?c='':"vzaar"===f.type&&(c=''),a('
'+c+"
").insertAfter(e.find(".owl-video")),this._playing=e.addClass("owl-video-playing"))},e.prototype.isInFullScreen=function(){var b=c.fullscreenElement||c.mozFullScreenElement||c.webkitFullscreenElement;return b&&a(b).parent().hasClass("owl-video-frame")},e.prototype.destroy=function(){var a,b;this._core.$element.off("click.owl.video");for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Video=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this.core=b,this.core.options=a.extend({},e.Defaults,this.core.options),this.swapping=!0,this.previous=d,this.next=d,this.handlers={"change.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&(this.previous=this.core.current(),this.next=a.property.value)},this),"drag.owl.carousel dragged.owl.carousel translated.owl.carousel":a.proxy(function(a){a.namespace&&(this.swapping="translated"==a.type)},this),"translate.owl.carousel":a.proxy(function(a){a.namespace&&this.swapping&&(this.core.options.animateOut||this.core.options.animateIn)&&this.swap()},this)},this.core.$element.on(this.handlers)};e.Defaults={animateOut:!1,animateIn:!1},e.prototype.swap=function(){if(1===this.core.settings.items&&a.support.animation&&a.support.transition){this.core.speed(0);var b,c=a.proxy(this.clear,this),d=this.core.$stage.children().eq(this.previous),e=this.core.$stage.children().eq(this.next),f=this.core.settings.animateIn,g=this.core.settings.animateOut;this.core.current()!==this.previous&&(g&&(b=this.core.coordinates(this.previous)-this.core.coordinates(this.next),d.one(a.support.animation.end,c).css({left:b+"px"}).addClass("animated owl-animated-out").addClass(g)),f&&e.one(a.support.animation.end,c).addClass("animated owl-animated-in").addClass(f))}},e.prototype.clear=function(b){a(b.target).css({left:""}).removeClass("animated owl-animated-out owl-animated-in").removeClass(this.core.settings.animateIn).removeClass(this.core.settings.animateOut),this.core.onTransitionEnd()},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this.core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null); },a.fn.owlCarousel.Constructor.Plugins.Animate=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._timeout=null,this._paused=!1,this._handlers={"changed.owl.carousel":a.proxy(function(a){a.namespace&&"settings"===a.property.name?this._core.settings.autoplay?this.play():this.stop():a.namespace&&"position"===a.property.name&&this._core.settings.autoplay&&this._setAutoPlayInterval()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoplay&&this.play()},this),"play.owl.autoplay":a.proxy(function(a,b,c){a.namespace&&this.play(b,c)},this),"stop.owl.autoplay":a.proxy(function(a){a.namespace&&this.stop()},this),"mouseover.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"mouseleave.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.play()},this),"touchstart.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"touchend.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this.play()},this)},this._core.$element.on(this._handlers),this._core.options=a.extend({},e.Defaults,this._core.options)};e.Defaults={autoplay:!1,autoplayTimeout:5e3,autoplayHoverPause:!1,autoplaySpeed:!1},e.prototype.play=function(a,b){this._paused=!1,this._core.is("rotating")||(this._core.enter("rotating"),this._setAutoPlayInterval())},e.prototype._getNextTimeout=function(d,e){return this._timeout&&b.clearTimeout(this._timeout),b.setTimeout(a.proxy(function(){this._paused||this._core.is("busy")||this._core.is("interacting")||c.hidden||this._core.next(e||this._core.settings.autoplaySpeed)},this),d||this._core.settings.autoplayTimeout)},e.prototype._setAutoPlayInterval=function(){this._timeout=this._getNextTimeout()},e.prototype.stop=function(){this._core.is("rotating")&&(b.clearTimeout(this._timeout),this._core.leave("rotating"))},e.prototype.pause=function(){this._core.is("rotating")&&(this._paused=!0)},e.prototype.destroy=function(){var a,b;this.stop();for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.autoplay=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(b){this._core=b,this._initialized=!1,this._pages=[],this._controls={},this._templates=[],this.$element=this._core.$element,this._overrides={next:this._core.next,prev:this._core.prev,to:this._core.to},this._handlers={"prepared.owl.carousel":a.proxy(function(b){b.namespace&&this._core.settings.dotsData&&this._templates.push('
'+a(b.content).find("[data-dot]").addBack("[data-dot]").attr("data-dot")+"
")},this),"added.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,0,this._templates.pop())},this),"remove.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,1)},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&this.draw()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&!this._initialized&&(this._core.trigger("initialize",null,"navigation"),this.initialize(),this.update(),this.draw(),this._initialized=!0,this._core.trigger("initialized",null,"navigation"))},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._initialized&&(this._core.trigger("refresh",null,"navigation"),this.update(),this.draw(),this._core.trigger("refreshed",null,"navigation"))},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers)};e.Defaults={nav:!1,navText:["prev","next"],navSpeed:!1,navElement:"div",navContainer:!1,navContainerClass:"owl-nav",navClass:["owl-prev","owl-next"],slideBy:1,dotClass:"owl-dot",dotsClass:"owl-dots",dots:!0,dotsEach:!1,dotsData:!1,dotsSpeed:!1,dotsContainer:!1},e.prototype.initialize=function(){var b,c=this._core.settings;this._controls.$relative=(c.navContainer?a(c.navContainer):a("
").addClass(c.navContainerClass).appendTo(this.$element)).addClass("disabled"),this._controls.$previous=a("<"+c.navElement+">").addClass(c.navClass[0]).html(c.navText[0]).prependTo(this._controls.$relative).on("click",a.proxy(function(a){this.prev(c.navSpeed)},this)),this._controls.$next=a("<"+c.navElement+">").addClass(c.navClass[1]).html(c.navText[1]).appendTo(this._controls.$relative).on("click",a.proxy(function(a){this.next(c.navSpeed)},this)),c.dotsData||(this._templates=[a("
").addClass(c.dotClass).append(a("")).prop("outerHTML")]),this._controls.$absolute=(c.dotsContainer?a(c.dotsContainer):a("
").addClass(c.dotsClass).appendTo(this.$element)).addClass("disabled"),this._controls.$absolute.on("click","div",a.proxy(function(b){var d=a(b.target).parent().is(this._controls.$absolute)?a(b.target).index():a(b.target).parent().index();b.preventDefault(),this.to(d,c.dotsSpeed)},this));for(b in this._overrides)this._core[b]=a.proxy(this[b],this)},e.prototype.destroy=function(){var a,b,c,d;for(a in this._handlers)this.$element.off(a,this._handlers[a]);for(b in this._controls)this._controls[b].remove();for(d in this.overides)this._core[d]=this._overrides[d];for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},e.prototype.update=function(){var a,b,c,d=this._core.clones().length/2,e=d+this._core.items().length,f=this._core.maximum(!0),g=this._core.settings,h=g.center||g.autoWidth||g.dotsData?1:g.dotsEach||g.items;if("page"!==g.slideBy&&(g.slideBy=Math.min(g.slideBy,g.items)),g.dots||"page"==g.slideBy)for(this._pages=[],a=d,b=0,c=0;e>a;a++){if(b>=h||0===b){if(this._pages.push({start:Math.min(f,a-d),end:a-d+h-1}),Math.min(f,a-d)===f)break;b=0,++c}b+=this._core.mergers(this._core.relative(a))}},e.prototype.draw=function(){var b,c=this._core.settings,d=this._core.items().length<=c.items,e=this._core.relative(this._core.current()),f=c.loop||c.rewind;this._controls.$relative.toggleClass("disabled",!c.nav||d),c.nav&&(this._controls.$previous.toggleClass("disabled",!f&&e<=this._core.minimum(!0)),this._controls.$next.toggleClass("disabled",!f&&e>=this._core.maximum(!0))),this._controls.$absolute.toggleClass("disabled",!c.dots||d),c.dots&&(b=this._pages.length-this._controls.$absolute.children().length,c.dotsData&&0!==b?this._controls.$absolute.html(this._templates.join("")):b>0?this._controls.$absolute.append(new Array(b+1).join(this._templates[0])):0>b&&this._controls.$absolute.children().slice(b).remove(),this._controls.$absolute.find(".active").removeClass("active"),this._controls.$absolute.children().eq(a.inArray(this.current(),this._pages)).addClass("active"))},e.prototype.onTrigger=function(b){var c=this._core.settings;b.page={index:a.inArray(this.current(),this._pages),count:this._pages.length,size:c&&(c.center||c.autoWidth||c.dotsData?1:c.dotsEach||c.items)}},e.prototype.current=function(){var b=this._core.relative(this._core.current());return a.grep(this._pages,a.proxy(function(a,c){return a.start<=b&&a.end>=b},this)).pop()},e.prototype.getPosition=function(b){var c,d,e=this._core.settings;return"page"==e.slideBy?(c=a.inArray(this.current(),this._pages),d=this._pages.length,b?++c:--c,c=this._pages[(c%d+d)%d].start):(c=this._core.relative(this._core.current()),d=this._core.items().length,b?c+=e.slideBy:c-=e.slideBy),c},e.prototype.next=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!0),b)},e.prototype.prev=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!1),b)},e.prototype.to=function(b,c,d){var e;!d&&this._pages.length?(e=this._pages.length,a.proxy(this._overrides.to,this._core)(this._pages[(b%e+e)%e].start,c)):a.proxy(this._overrides.to,this._core)(b,c)},a.fn.owlCarousel.Constructor.Plugins.Navigation=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(c){this._core=c,this._hashes={},this.$element=this._core.$element,this._handlers={"initialized.owl.carousel":a.proxy(function(c){c.namespace&&"URLHash"===this._core.settings.startPosition&&a(b).trigger("hashchange.owl.navigation")},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find("[data-hash]").addBack("[data-hash]").attr("data-hash");if(!c)return;this._hashes[c]=b.content}},this),"changed.owl.carousel":a.proxy(function(c){if(c.namespace&&"position"===c.property.name){var d=this._core.items(this._core.relative(this._core.current())),e=a.map(this._hashes,function(a,b){return a===d?b:null}).join();if(!e||b.location.hash.slice(1)===e)return;b.location.hash=e}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers),a(b).on("hashchange.owl.navigation",a.proxy(function(a){var c=b.location.hash.substring(1),e=this._core.$stage.children(),f=this._hashes[c]&&e.index(this._hashes[c]);f!==d&&f!==this._core.current()&&this._core.to(this._core.relative(f),!1,!0)},this))};e.Defaults={URLhashListener:!1},e.prototype.destroy=function(){var c,d;a(b).off("hashchange.owl.navigation");for(c in this._handlers)this._core.$element.off(c,this._handlers[c]);for(d in Object.getOwnPropertyNames(this))"function"!=typeof this[d]&&(this[d]=null)},a.fn.owlCarousel.Constructor.Plugins.Hash=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a.each((b+" "+h.join(f+" ")+f).split(" "),function(a,b){return g[b]!==d?(e=c?b:!0,!1):void 0}),e}function f(a){return e(a,!0)}var g=a("").get(0).style,h="Webkit Moz O ms".split(" "),i={transition:{end:{WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}},animation:{end:{WebkitAnimation:"webkitAnimationEnd",MozAnimation:"animationend",OAnimation:"oAnimationEnd",animation:"animationend"}}},j={csstransforms:function(){return!!e("transform")},csstransforms3d:function(){return!!e("perspective")},csstransitions:function(){return!!e("transition")},cssanimations:function(){return!!e("animation")}};j.csstransitions()&&(a.support.transition=new String(f("transition")),a.support.transition.end=i.transition.end[a.support.transition]),j.cssanimations()&&(a.support.animation=new String(f("animation")),a.support.animation.end=i.animation.end[a.support.animation]),j.csstransforms()&&(a.support.transform=new String(f("transform")),a.support.transform3d=j.csstransforms3d())}(window.Zepto||window.jQuery,window,document);doit-0.36.0/doc/_static/vendor/owl.carousel/owl.theme.default.min.css000066400000000000000000000017531423054503100254700ustar00rootroot00000000000000/** * Owl Carousel v2.2.0 * Copyright 2013-2016 David Deutsch * Licensed under MIT (https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE) */ .owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}doit-0.36.0/doc/_templates/000077500000000000000000000000001423054503100154365ustar00rootroot00000000000000doit-0.36.0/doc/_templates/donate.html000066400000000000000000000027221423054503100176010ustar00rootroot00000000000000{% block extranav %} {% if include_donate %}

Donate

If you find doit useful, please consider supporting the maintainer with a donation.

doit is 100% open-source and maintained by individuals without any company backing it... Your donation keeps the project healthy and maintained.

OpenCollective


Paypal


{% endif %} {% endblock %} doit-0.36.0/doc/_templates/layout.html000066400000000000000000000016771423054503100176540ustar00rootroot00000000000000{% extends "!layout.html" %} {% block footer_scripts %} {{ super() }} {% block analytics %} {% if include_analytics %} {% endif %} {% endblock %} {% endblock %} {# FIXME show version #} {#
#} {# {% if theme_display_version %} #} {# {%- set nav_version = version %} #} {# {% if nav_version %} #} {# {{ nav_version }} #} {# {% endif %} #} {# {% endif %} #} {#
#} doit-0.36.0/doc/changes.rst000077700000000000000000000000001423054503100166442../CHANGESustar00rootroot00000000000000doit-0.36.0/doc/cmd-other.rst000066400000000000000000000272361423054503100157270ustar00rootroot00000000000000.. meta:: :description: doit provides several sub-commands to introspect and manipulated defined tasks :keywords: python, doit, documentation, guide, task, list, auto, introspection .. title:: A survey of doit sub-commands for task manipulation ================================== sub-commands for task manipulation ================================== .. note:: Not all options/arguments are documented below. Always check `doit help ` to see a complete list of options. Let's use a more complex example to demonstrate the command line features. The example below is used to manage a very simple C project. .. literalinclude:: samples/cproject.py .. _cmd-help: help ------- `doit` comes with several commands. `doit help` will list all available commands. You can also get help from each available command. e.g. `doit help run`. `doit help task` will display information on all fields/attributes a task dictionary from a `dodo` file accepts. .. _cmd-list: list ------ *list* is used to show all tasks available in a *dodo* file. Tasks are listed in alphabetical order by default, but *--sort=definition* can be specified to sort them in the order in which they appear in the `dodo` file. .. code-block:: console $ doit list compile : compile C files install : install executable (TODO) link : create binary program By default task name and description are listed. The task description is taken from the first line of task function doc-string. You can also set it using the *doc* attribute on the task dictionary. It is possible to omit the description using the option *-q*/*--quiet*. By default sub-tasks are not listed. It can list sub-tasks using the option *--all*. By default task names that start with an underscore(*_*) are not listed. They are listed if the option *-p*/*--private* is used. Task's file-dependencies can be printed using the option *--deps*. status ^^^^^^ If you would like to know if a task would actually be executed, the option *-s*/*--status* can be used to display the task's status . It is one of: - ``R``: run - ``U``: up-to-date - ``I``: ignored This is an alternative to *dry-run* or *preflight* feature provided by some tools. info ---- You can check a task meta-data using the *info* command. This might be useful when have some complex code generating the task meta-data. .. code-block:: console $ doit info link link status : up-to-date file_dep: - command.o - kbd.o - main.o targets: - edit Note that if the task is **not** *up-to-date* the reason a task is not up-to-date. .. code-block:: console $ doit info link link status : run * The following file dependencies have changed: - main.o - kbd.o - command.o forget ------- Suppose you change the compilation parameters in the compile action. Or you changed the code from a python-action. *doit* will think your task is up-to-date based on the dependencies but actually it is not! In this case you can use the *forget* command to make sure the given task will be executed again even with no changes in the dependencies. If you do not specify any task, the default tasks are "*forget*". .. code-block:: console $ doit forget .. note:: *doit* keeps track of which tasks are successful in the file ``.doit.db``. --disable-default ^^^^^^^^^^^^^^^^^ If your default tasks are expensive, you can avoid accidentally forgetting all of your default tasks by setting ``forget_disable_default = True`` in ``doit.cfg``. You can explicitly forget default tasks with ``--enable-default``. --all ^^^^^ Use ``doit forget --all`` to forget *all* tasks. clean ------ A common scenario is a task that needs to "revert" its actions. A task may include a *clean* attribute. This attribute can be ``True`` to remove all of its target files. If there is a folder as a target it will be removed if the folder is empty, otherwise it will display a warning message. .. note:: The targets' removal order will be the reverse of their lexical ordering. This ensures that files in a directory are removed before the directory irrespective of their order in the ``targets`` array. The *clean* attribute can be a list of actions. An action could be a string with a shell command or a tuple with a python callable. If you want to clean the targets and add some custom clean actions, you can include the `doit.task.clean_targets` instead of passing `True`: .. literalinclude:: samples/clean_mix.py You can specify which task to *clean*. If no task is specified the clean operation of default tasks are executed. .. code-block:: console $ doit clean By default if a task contains task-dependencies those are not automatically cleaned too. You can enable this using the option *-c*/*--clean-dep*. If you are executing the default tasks this flag is automatically set. .. note:: By default only the default tasks' clean are executed, not from all tasks. You can clean all tasks using the *-a*/*--all* argument. If you like to also make doit forget previous execution of cleaned tasks, use option *--forget*. This can be made the default behavior by adding the corresponding ``cleanforget`` configuration switch: .. code-block:: python DOIT_CONFIG = { 'cleanforget': True, } dry run ^^^^^^^ If you want check which tasks the clean operation would affect you can use the option `-n/--dry-run`. When using a custom action on `dry-run`, the action is not executed at all **if** it does not include a `dryrun` parameter. If it includes a `dryrun` parameter the action will **always** be executed, and its implementation is responsible for handling the *dry-run* logic. .. literalinclude:: samples/custom_clean.py ignore ------- It is possible to set a task to be ignored/skipped (that is, not executed). This is useful, for example, when you are performing checks in several files and you want to skip the check in some of them temporarily. .. literalinclude:: samples/subtasks.py .. code-block:: console $ doit . create_file:file0.txt . create_file:file1.txt . create_file:file2.txt $ doit ignore create_file:file1.txt ignoring create_file:file1.txt $ doit . create_file:file0.txt !! create_file:file1.txt . create_file:file2.txt Note the ``!!``, it means that task was ignored. To reverse the `ignore` use `forget` sub-command. .. _cmd-auto: auto (watch) ------------- .. note:: Supported on Linux and Mac only. `auto` is provided through the package `doit-auto1 `_ plugin. To install it:: $ pip install doit-auto1 `auto` sub-command is an alternative way of executing your tasks. It is a long running process that only terminates when it is interrupted `Ctrl-C`. When started it will execute the given tasks. After that it will watch the file system for modifications in the file-dependencies. When a file is modified the tasks are re-executed. .. code-block:: console $ doit auto .. note:: The `dodo` file is actually re-loaded/executed in a separate process every time tasks need to be re-executed. callbacks ^^^^^^^^^ It is possible to specify shell commands to executed after every cycle of task execution. This can used to display desktop notifications, so you do not need to keep an eye in the terminal to notice when tasks succeed or failed. Example of sound and desktop notification on Ubuntu. Contents of a `pyproject.toml` file: .. code-block:: toml [tool.doit.commands.auto] success_callback = """ notify-send -u low -i /usr/share/icons/gnome/16x16/emotes/face-smile.png "doit: success"; aplay -q /usr/share/sounds/purple/send.wav """ failure_callback = """ notify-send -u normal -i /usr/share/icons/gnome/16x16/status/error.png "doit: fail"; aplay -q /usr/share/sounds/purple/alert.wav """ Contents of a `doit.cfg` file: .. code-block:: ini [auto] success_callback = notify-send -u low -i /usr/share/icons/gnome/16x16/emotes/face-smile.png "doit: success"; aplay -q /usr/share/sounds/purple/send.wav failure_callback = notify-send -u normal -i /usr/share/icons/gnome/16x16/status/error.png "doit: fail"; aplay -q /usr/share/sounds/purple/alert.wav ``watch`` parameter ^^^^^^^^^^^^^^^^^^^^^ Apart from ``file_dep`` you can use the parameter ``watch`` to pass extra paths to be watched for (including folders). If paths are folders their sub-folders will not be watched unless these sub-folders are also part of the given extra paths. The ``watch`` parameter can also be specified for a group of "sub-tasks". .. literalinclude:: samples/empty_subtasks.py .. _tabcompletion: tabcompletion ---------------- This command creates a completion for bash or zsh. The generated script is written on stdout. bash ^^^^^^ To use a completion script you need to `source` it first. .. code-block:: console $ doit tabcompletion > bash_completion_doit $ source bash_completion_doit zsh ^^^^^ zsh completion scripts should be placed in a folder in the "autoload" path. .. code-block:: sh # add folder with completion scripts fpath=(~/.zsh/tabcompletion $fpath) # Use modern completion system autoload -Uz compinit compinit .. code-block:: console $ doit tabcompletion --shell zsh > _doit $ cp _doit ~/.zsh/tabcompletion/_doit hard-coding tasks ^^^^^^^^^^^^^^^^^^^^ If you are creating an application based on `doit` or if you tasks take a long time to load you may create a completion script that includes the list of tasks from your dodo.py. .. code-block:: console $ my_app tabcompletion --hardcode-tasks > _my_app dumpdb -------- `doit` saves internal data in a file (`.doit.db` by default). It uses a binary format (whatever python's dbm is using in your system). This command will simply dump its content in readable text format in the output. .. code-block:: console $ doit dumpdb strace -------- This command uses `strace `_ utility to help you verify which files are being used by a given task. The output is a list of files prefixed with `R` for open in read mode or `W` for open in write mode. The files are listed in chronological order. This is a debugging feature with many limitations. * can strace only one task at a time * can only strace CmdAction * the process being traced itself might have some kind of cache, that means it might not write a target file if it exist * does not handle chdir So this is NOT 100% reliable, use with care! .. code-block:: console $ doit strace reset-dep --------- This command allows to recompute the information on file dependencies (timestamp, md5sum, ... depending on the ``check_file_uptodate`` setting), and save this in the database, without executing the actions. The command run on all tasks by default, but it is possible to specify a list of tasks to work on. This is useful when the targets of your tasks already exist, and you want doit to consider your tasks as up-to-date. One use-case for this command is when you change the ``check_file_uptodate`` setting, which cause doit to consider all your tasks as not up-to-date. It is also useful if you start using doit while some of your data as already been computed, or when you add a file dependency to a task that has already run. .. code-block:: console $ doit reset-dep .. warning:: `reset-dep` will **NOT** recalculate task `values` and `result`. This might not be the correct behavior for your tasks! It is safe to use `reset-dep` if your tasks rely only on files to control its up-to-date status. So only use this command if you are sure it is OK for your tasks. If the DB already has any saved `values` or `result` they will be preserved otherwise they will not be set at all. doit-0.36.0/doc/cmd-run.rst000066400000000000000000000264561423054503100154150ustar00rootroot00000000000000 .. meta:: :description: pydoit guide - CLI usage and options :keywords: python, doit, documentation, guide, task-runner .. title:: pydoit CLI usage and options - command run ============================== Command line interface - run ============================== A general `doit` command goes like this: .. code-block:: console $ doit [run] [] [ ]* [] The `doit` command line contains several sub-commands. Most of the time you just want to execute your tasks, that's what *run* does. Since it is by far the most common operation it is also the default, so if you don't specify any sub-command to *doit* it will execute *run*. So ``$ doit`` and ``$ doit run`` do the same thing. The basics of task selection were introduced in :ref:`Task Selection `. `python -m doit` ----------------- `doit` can also be executed without using the `doit` script. .. code-block:: console $ python -m doit This is specially useful when testing `doit` with different python versions. dodo file ---------- By default all commands are relative to ``dodo.py`` in the current folder. You can specify a different *dodo* file containing task with the flag ``-f``. This flag is valid for all sub-commands. .. code-block:: console $ doit -f release.py *doit* can seek for the ``dodo.py`` file on parent folders if the option ``--seek-file`` is specified. as an executable file ----------------------- using a hashbang ^^^^^^^^^^^^^^^^^^^^^ If you have `doit` installed on ``/usr/bin`` use the following hashbang: .. code-block:: bash #! /usr/bin/doit -f using the API ^^^^^^^^^^^^^^ It is possible to make a ``dodo`` file become an executable on its own by calling the ``doit.run()``, you need to pass the ``globals``: .. literalinclude:: samples/executable.py .. note:: The ``doit.run()`` method will call ``sys.exit()`` so any code after it will not be executed. ``doit.run()`` parameter will be passed to a :ref:`ModuleTaskLoader ` to find your tasks. from IPython ------------------ You can install and use the `%doit` magic function to load tasks defined directly in IPython's global namespace (:ref:`more `). returned value ------------------ ``doit`` process returns: * 0 => all tasks executed successfully * 1 => task failed * 2 => error executing task * 3 => error before task execution starts (in this case the reporter is not used) .. _db_backends: DB backend -------------- `doit` saves the results of your tasks runs in a "DB-file", it supports different backends: - `dbm`: (default) It uses `python dbm module `_. The actual DBM used depends on what is available on your machine/platform. - `json`: Plain text using a json structure, it is slow but good for debugging. - `sqlite3`: Support concurrent access (DB is updated only once when process is terminated for better performance). From the command line you can select the backend using the ``--backend`` option. It is quite easy to add a new backend for any key-value store. .. warning: `dbm` modules do not support concurrent access through different processes. `dbm.dumb` will even cause file corruption! DB-file ---------- Option ``--db-file`` sets the name of the file to save the "DB", default is ``.doit.db``. Note that DBM backends might save more than one file, in this case the specified name is used as a base name. To configure in a `dodo` file the field name is ``dep_file`` .. code-block:: python DOIT_CONFIG = { 'backend': 'json', 'dep_file': 'doit-db.json', } .. _verbosity_option: verbosity ----------- Option to change the default global task :ref:`verbosity` value. .. code-block:: console $ doit --verbosity 2 failure-verbosity ----------------- Option to control if stdout/stderr should be re-displayed in the end of of report. This is useful when used in conjunction with `--continue` option. .. code-block:: console $ doit --failure-verbosity 1 output buffering ---------------- The output (`stdout` and `stderr`) is by default line-buffered for `CmdAction`. You can change that by specifying the `buffering` parameter when creating a `CmdAction`. The value zero (the default) means line-buffered, positive integers are the number of bytes to be read per call. Note this controls the buffering from the `doit` process and the terminal, not to be confused with subprocess.Popen `buffered`. .. code-block:: python from doit.action import CmdAction def task_progress(): return { 'actions': [CmdAction("progress_bar", buffering=1)], } dir (cwd) ----------- By default the directory of the `dodo` file is used as the "current working directory" on python execution. You can specify a different *cwd* with the *-d*/*--dir* option. .. code-block:: console $ doit --dir path/to/another/cwd .. note:: It is possible to get a reference to the original initial current working directory (location where the command line was executed) using :ref:`initial_workdir`. continue --------- By default the execution of tasks is halted on the first task failure or error. You can force it to continue execution with the option --continue/-c .. code-block:: console $ doit --continue single task execution ---------------------- The option ``-s/--single`` can be used to execute a task without executing its task dependencies. .. code-block:: console $ doit -s do_something .. _parallel-execution: parallel execution ------------------- `doit` supports parallel execution --process/-n. This allows different tasks to be run in parallel, as long any dependencies are met. By default the `multiprocessing `_ module is used. So the same restrictions also apply to the use of multiprocessing in `doit`. .. code-block:: console $ doit -n 3 You can also execute in parallel using threads by specifying the option `--parallel-type/-P`. .. code-block:: console $ doit -n 3 -P thread .. note:: The actions of a single task are always run sequentially; only tasks and sub-tasks are affected by the parallel execution option. .. warning:: On Windows, due to some limitations on how `multiprocess` works, there are stricter requirements for task properties being picklable than other platforms. .. _reporter: reporter --------- `doit` provides different "*reporters*" to display running tasks info on the console. Use the option --reporter/-r to choose a reporter. Apart from the default it also includes: * executed-only: Produces zero output if no task is executed * json: Output results in JSON format * zero: display only error messages (does not display info on tasks being executed/skipped). This is used when you only want to see the output generated by the tasks execution. .. code-block:: console $ doit --reporter json .. _custom_reporter: custom reporter ----------------- It is possible to define your own custom reporter. Check the code on `doit/reporter.py `_ ... It is easy to get started by sub-classing the default reporter as shown below. The custom reporter can be enabled directly on DOIT_CONFIG dict. .. literalinclude:: samples/custom_reporter.py It is also possible distribute/use a custom reporter as a :ref:`plugin `. Note that the ``reporter`` have no control over the *real time* output from a task while it is being executed, this is controlled by the ``verbosity`` param. check_file_uptodate ------------------- `doit` provides different options to check if dependency files are up to date (see :ref:`file-dep`). Use the option ``--check_file_uptodate`` to choose: * `md5`: use the md5sum. * `timestamp`: use the timestamp. .. note:: The `timestamp` checker considers a file is not up-to-date if there is **any** change in the the modified time (`mtime`), it does not matter if the new time is in the future or past of the original timestamp. You can set this option from command line, but you probably want to set it for all commands using `DOIT_CONFIG`. .. code-block:: console DOIT_CONFIG = {'check_file_uptodate': 'timestamp'} custom check_file_uptodate ^^^^^^^^^^^^^^^^^^^^^^^^^^ It is possible to define your own custom up to date checker. Check the code on `doit/dependency.py `_ ... Sub-class ``FileChangedChecker`` and define the 2 required methods as shown below. The custom checker must be configured using DOIT_CONFIG dict. .. code-block:: python from doit.dependency import FileChangedChecker class MyChecker(FileChangedChecker): """With this checker, files are always out of date.""" def check_modified(self, file_path, file_stat, state): return True def get_state(self, dep, current_state): pass DOIT_CONFIG = {'check_file_uptodate': MyChecker} output-file ------------ The option --output-file/-o let you output the result to a file. .. code-block:: console $ doit --output-file result.txt pdb ------- If the option ``--pdb`` is used, a post-mortem debugger will be launched in case of a unhandled exception while loading tasks. .. _initial_workdir: get_initial_workdir() --------------------- When `doit` executes by default it will use the location of `dodo.py` as the current working directory (unless --dir is specified). The value of `doit.get_initial_workdir()` will contain the path from where `doit` was invoked from. This can be used for example set which tasks will be executed: .. literalinclude:: samples/initial_workdir.py codec_cls --------- `doit` uses the ``json`` package to serialize and deserialize values returned by python-actions. If required an alternate encoder/decoder pair may be specified so that return values which are not JSON serializable, such as Python class instances, may be provided as return values from python-actions. Check ``dependency.py`` 's ``JSONCodec`` interface. minversion ------------- `minversion` can be used to specify the minimum/oldest `doit` version that can be used with a `dodo.py` file. For example if your `dodo.py` makes use of a feature added at `doit X` and distribute it. If another user who tries this `dodo.py` with a version older that `X`, doit will display an error warning the user to update `doit`. `minversion` can be specified as a string or a 3-element tuple with integer values. If specified as a string any part that is not a number i.e.(dev0, a2, b4) will be converted to -1. .. code-block:: console DOIT_CONFIG = { 'minversion': '0.24.0', } .. note:: This feature was added on `doit` 0.24.0. Older Versions will not check or display error messages. .. _auto-delayed-regex: automatic regex for delayed task loaders ------------------------------------------ When specifying a target for `doit run`, *doit* usually only considers usual tasks and :ref:`delayed tasks ` which have a target regex specified. Any task generated by a delayed task loader which has :ref:`no target regex specified ` will not be considered. By specifying `--auto-delayed-regex`, every delayed task loader having no target regex specified is assumed to have `.*` specified, a regex which matches any target. doit-0.36.0/doc/conf.py000066400000000000000000000132571423054503100146100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'doit' copyright = '2008-2020, Eduardo Schettino' author = 'Eduardo Schettino (schettino72)' # The short X.Y version version = '0.36' # The full version, including alpha/beta/rc tags release = '0.36.0' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx_sitemap', 'sphinx_reredirects', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'contents' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = ['_build', 'presentation.rst', 'old'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # non-default configuration for doit html_favicon = '_static/favico.ico' html_show_sourcelink = False html_extra_path = ['index.html', 'robots.txt', 'google726fc03ab55ebbfc.html'] html_logo = '_static/doit-logo-small.png' # -- Options for HTML output ------------------------------------------------- html_baseurl = 'https://pydoit.org' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'press' #'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # html_sidebars = { '**': [ 'util/searchbox.html', 'util/sidetoc.html', 'donate.html' ] } html_theme_options = { "external_links": [ ("Twitter", "https://twitter.com/pydoit"), ("Github", "https://github.com/pydoit/doit"), ], } # configuration for sphinx_sitemap extension sitemap_url_scheme = "{link}" redirects = { "tutorial_1": "tutorial-1.html", "cmd_other": "cmd-other.html", "cmd_run": "cmd-run.html", "task_args": "task-args.html", "task_creation": "task-creation.html", } # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'doitdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'doit.tex', 'doit Documentation', 'schettino72', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'doit', 'doit Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'doit', 'doit Documentation', author, 'doit', 'One line description of project.', 'Miscellaneous'), ] # -- Extension configuration ------------------------------------------------- doit-0.36.0/doc/configuration.rst000066400000000000000000000117061423054503100167070ustar00rootroot00000000000000.. meta:: :description: Configuring pydoit with TOML and INI files :keywords: python, doit, documentation, guide, configuration, ini, toml .. title:: Configuration with TOML and INI files - pydoit guide Configuration ============= config param names ------------------ Note that the configuration option's name is not always the same as the *long* argument name used in the command line. I.e. To specify dodo file other than `dodo.py` from the command line you specify the option as ``-f`` or ``--file``, but from a config file it is called ``dodoFile``. The name can be seem from ``doit help`` output:: -f ARG, --file=ARG load task from dodo FILE [default: dodo.py] (config: dodoFile) pyproject.toml -------------- `doit` configuration can be read from `pyproject.toml `_ under the `tool.doit` namespace. This is the preferred configuration source, and may gain features not available in the legacy `doit.cfg`. .. note:: A TOML parser (`tomllib `_) is part of the standard library since Python 3.11. For earlier Python versions, a third-party package is required, one of: - `tomli `_ - `tomlkit `_ TOML vs INI ^^^^^^^^^^^ While mostly similar, `TOML `_ differs from the INI format in a few ways: - all strings must be quoted with `'` or `"` - triple-quoted strings may contain new line characters (`\n`) and quotes - must be saved as UTF-8 - integers and floating point numbers can be written without quotes - boolean values can be written unquoted and lower-cased, as `true` and `false` Unlike "plain" TOML, `doit` will parse pythonic strings into their correct types, e.g. `"True"`, `"False"`, `"3"`, but using "native" TOML types may be preferable. tool.doit ^^^^^^^^^ The `tool.doit` section may contain command line options that will be used (if applicable) by any commands. Example setting the DB backend type: .. code-block:: toml [tool.doit] backend = "json" All commands that have a `backend` option (*run*, *clean*, *forget*, etc), will use this option without the need for this option in the command line. tool.doit.commands ^^^^^^^^^^^^^^^^^^ To configure options for a specific command, use a section with the command name under `tool.doit.commands`: .. code-block:: toml [tools.doit.commands.list] status = true subtasks = true tool.doit.plugins ^^^^^^^^^^^^^^^^^ Check the :ref:`plugins ` section for an introduction on available plugin categories. tool.doit.tasks ^^^^^^^^^^^^^^^ To configure options for a specific task, use a section with the task name under `tool.doit.tasks`: .. code-block:: toml [tool.doit.tasks.make_cookies] cookie_type = "chocolate" temp = "375F" duration = 12 doit.cfg -------- `doit` also supports an INI style configuration file (see `configparser `_). Note: key/value entries can be separated only by the equal sign `=`. If a file name `doit.cfg` is present in the current working directory, it is processed. It supports 4 kind of sections: - a `GLOBAL` section - a section for each plugin category - a section for each command - a section for each task GLOBAL section ^^^^^^^^^^^^^^ The `GLOBAL` section may contain command line options that will be used (if applicable) by any commands. Example setting the DB backend type: .. code-block:: ini [GLOBAL] backend = json All commands that have a `backend` option (*run*, *clean*, *forget*, etc), will use this option without the need for this option in the command line. commands section ^^^^^^^^^^^^^^^^ To configure options for a specific command, use a section with the command name: .. code-block:: ini [list] status = True subtasks = True plugins sections ^^^^^^^^^^^^^^^^ Check the :ref:`plugins ` section for an introduction on available plugin categories. per-task sections ^^^^^^^^^^^^^^^^^ To configure options for a specific task, use a section with the task name prefixed with "task:": .. code-block:: ini [task:make_cookies] cookie_type = chocolate temp = 375F duration = 12 configuration at *dodo.py* -------------------------- As a convenience you can also set `GLOBAL` options directly into a `dodo.py`. Just put the option in the `DOIT_CONFIG` dict. This example below sets the default tasks to be run, the ``continue`` option, and a different reporter. .. literalinclude:: samples/doit_config.py So if you just execute .. code-block:: console $ doit it will have the same effect as executing .. code-block:: console $ doit --continue --reporter json my_task_1 my_task_2 .. note:: Not all options can be set on `dodo.py` file. The parameters ``--file`` and ``--dir`` can not be used on config because they control how the *dodo* file itself is loaded. Also if the command does not read the `dodo.py` file it obviously will not be used. doit-0.36.0/doc/contents.rst000066400000000000000000000024431423054503100156730ustar00rootroot00000000000000.. doit documentation master file, created by sphinx-quickstart on Wed Apr 2 22:40:41 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. meta:: :description: Index of user documentation for pydoit - python task-runner & build-tool :keywords: python, doit, tutorial, documentation, task-runner, guide, how-to, getting started .. title:: documentation index | pydoit - python task-runner `doit` documentation ==================== Getting Started --------------- Introduction of `doit` basic features with a real example. .. toctree:: :maxdepth: 1 usecases tutorial-1 .. _main-ref-toc: Guide ----- Introduces the concepts of *every* feature one by one with examples. It was written to be both read in order and also serves as a complete reference. The total reading time for the whole documentation is about one hour. .. toctree:: :maxdepth: 2 install tasks dependencies task-creation cmd-run cmd-other configuration task-args globals uptodate tools extending Project ------- .. toctree:: :maxdepth: 2 support changes stories faq related .. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` doit-0.36.0/doc/dependencies.rst000066400000000000000000000325201423054503100164630ustar00rootroot00000000000000.. meta:: :description: pydoit guide - doit has a flexible dependency system, not exclusively based on files :keywords: python, doit, documentation, guide, task, dependency, incremental-build, up-to-date .. title:: pydoit task dependency system support - pydoit guide ===================== Non-file dependencies ===================== .. _attr-uptodate: uptodate ---------- Apart from file dependencies you can extend `doit` to support other ways to determine if a task is up-to-date through the attribute ``uptodate``. This can be used in cases where you need to some kind of calculation to determine if the task is up-to-date or not. ``uptodate`` is a list where each element can be True, False, None, a callable or a command(string). * ``False`` indicates that the task is NOT up-to-date * ``True`` indicates that the task is up-to-date * ``None`` values will just be ignored. This is used when the value is dynamically calculated .. note:: An ``uptodate`` value equal to ``True`` does not override others up-to-date checks. It is one more way to check if task is **not** up-to-date. i.e. if uptodate==True but a file_dep changes the task is still considered **not** up-to-date. If an ``uptodate`` item is a string it will be executed on the shell. If the process exits with the code ``0``, it is considered as up-to-date. All other values would be considered as not up-to-date. ``uptodate`` elements can also be a callable that will be executed on runtime (not when the task is being created). The section ``custom-uptodate`` will explain in details how to extend `doit` writing your own callables for ``uptodate``. This callables will typically compare a value on the present time with a value calculated on the last successful execution. .. note:: There is no guarantee ``uptodate`` callables or commands will be executed. `doit` short-circuit the checks, if it is already determined that the task is no `up-to-date` it will not execute remaining ``uptodate`` checks. `doit` includes several implementations to be used as ``uptodate``. They are all included in module `doit.tools` and will be discussed in detail :ref:`later `: * :ref:`result_dep `: check if the result of another task has changed * :ref:`run_once `: execute a task only once (used for tasks without dependencies) * :ref:`timeout `: indicate that a task should "expire" after a certain time interval * :ref:`config_changed `: check for changes in a "configuration" string or dictionary * :ref:`check_timestamp_unchanged`: check access, status change/create or modify timestamp of a given file/directory .. _up-to-date-def: doit up-to-date definition ----------------------------- A task is **not** up-to-date if any of: * an :ref:`uptodate ` item is (or evaluates to) `False` * a file is added to or removed from `file_dep` * a `file_dep` changed since last successful execution * a `target` path does not exist * a task has no `file_dep` and `uptodate` item equal to `True` It means that if a task does not explicitly define any *input* (dependency) it will never be considered `up-to-date`. Note that since a `target` represents an *output* of the task, a missing `target` is enough to determine that a task is not `up-to-date`. But its existence by itself is not enough to mark a task `up-to-date`. In some situations, it is useful to define a task with targets but no dependencies. If you want to re-execute this task only when targets are missing you must explicitly add a dependency: you could add a ``uptodate`` with ``True`` value or use :ref:`run_once() ` to force at least one execution managed by `doit`. Example: .. literalinclude:: samples/touch.py Apart from ``file_dep`` and ``uptodate`` used to determine if a task is `up-to-date` or not, ``doit`` also includes other kind of dependencies (introduced below) to help you combine tasks so they are executed in appropriate order. .. _uptodate_api: uptodate API -------------- This section will explain how to extend ``doit`` writing an ``uptodate`` implementation. So unless you need to write an ``uptodate`` implementation you can skip this. Let's start with trivial example. `uptodate` is a function that returns a boolean value. .. literalinclude:: samples/uptodate_callable.py You could also execute this function in the task-creator and pass the value to to `uptodate`. The advantage of just passing the callable is that this check will not be executed at all if the task was not selected to be executed. Example: run-once implementation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Most of the time an `uptodate` implementation will compare the current value of something with the value it had last time the task was executed. We already saw how tasks can save values by returning dict on its actions. But usually the "value" we want to check is independent from the task actions. So the first step is to add a callable to the task so it can save some extra values. These values are not used by the task itself, they are only used for dependency checking. The Task has a property called ``value_savers`` that contains a list of callables. These callables should return a dict that will be saved together with other task values. The ``value_savers`` will be executed after all actions. The second step is to actually compare the saved value with its "current" value. The `uptodate` callable can take two positional parameters ``task`` and ``values``. The callable can also be represented by a tuple (callable, args, kwargs). - ``task`` parameter will give you access to task object. So you have access to its metadata and opportunity to modify the task itself! - ``values`` is a dictionary with the computed values saved in the last successful execution of the task. Let's take a look in the ``run_once`` implementation. .. literalinclude:: samples/run_once.py The function ``save_executed`` returns a dict. In this case it is not checking for any value because it just checks it the task was ever executed. The next line we use the ``task`` parameter adding ``save_executed`` to ``task.value_savers``.So whenever this task is executed this task value 'run-once' will be saved. Finally the return value should be a boolean to indicate if the task is up-to-date or not. Remember that the 'values' parameter contains the dict with the values saved from last successful execution of the task. So it just checks if this task was executed before by looking for the ``run-once`` entry in ```values``. Example: timeout implementation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Let's look another example, the ``timeout``. The main difference is that we actually pass the parameter ``timeout_limit``. Here we present a simplified version that only accepts integers (seconds) as a parameter. .. code-block:: python class timeout(object): def __init__(self, timeout_limit): self.limit_sec = timeout_limit def __call__(self, task, values): def save_now(): return {'success-time': time_module.time()} task.value_savers.append(save_now) last_success = values.get('success-time', None) if last_success is None: return False return (time_module.time() - last_success) < self.limit_sec This is a class-based implementation where the objects are made callable by implementing a ``__call__`` method. On ``__init__`` we just save the ``timeout_limit`` as an attribute. The ``__call__`` is very similar with the ``run-once`` implementation. First it defines a function (``save_now``) that is registered into ``task.value_savers``. Than it compares the current time with the time that was saved on last successful execution. Example: result_dep implementation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``result_dep`` is more complicated due to two factors. It needs to modify the task's ``task_dep``. And it needs to check the task's saved values and metadata from a task different from where it is being applied. A ``result_dep`` implies that its dependency is also a ``task_dep``. We have seen that the callable takes a `task` parameter that we used to modify the task object. The problem is that modifying ``task_dep`` when the callable gets called would be "too late" according to the way `doit` works. When an object is passed ``uptodate`` and this object's class has a method named ``configure_task`` it will be called during the task creation. The base class ``dependency.UptodateCalculator`` gives access to an attribute named ``tasks_dict`` containing a dictionary with all task objects where the ``key`` is the task name (this is used to get all sub-tasks from a task-group). And also a method called ``get_val`` to access the saved values and results from any task. See the `result_dep` `source `_. task-dependency --------------- It is used to enforce tasks are executed on the desired order. By default tasks are executed on the same order as they were defined in the `dodo` file. To define a dependency on another task use the task name (whatever comes after ``task_`` on the function name) in the "task_dep" attribute. .. note:: A *task-dependency* **only** indicates that another task should be "executed" before itself. The task-dependency might not really be executed if it is *up-to-date*. .. note:: *task-dependencies* are **not** used to determine if a task is up-to-date or not. If a task defines only *task-dependency* it will always be executed. This example we make sure we include a file with the latest revision number of the mercurial repository on the tar file. .. literalinclude:: samples/tar.py .. code-block:: console $ doit . version . tar groups ^^^^^^^ You can define a group of tasks by adding tasks as dependencies and setting its `actions` to ``None``. .. literalinclude:: samples/group.py Note that tasks are never executed twice in the same "run". setup-task ------------- Some tasks may require some kind of environment setup. In this case they can define a list of "setup" tasks. * the setup-task will be executed only if the task is to be executed (not up-to-date) * setup-tasks are just normal tasks that follow all other task behavior .. note:: A *task-dependency* is executed before checking if the task is up-to-date. A *setup-task* is executed after the checking if the task is up-to-date and it is executed only if the task is not up-to-date and will be executed. teardown ^^^^^^^^^^^ Task may also define 'teardown' actions. These actions are executed after all tasks have finished their execution. They are executed in reverse order their tasks were executed. Example: .. literalinclude:: samples/tsetup.py .. code-block:: console $ doit withenvX . setup_sample:setupX start setupX . withenvX:c x c . withenvX:b x b . withenvX:a x a stop setupX $ doit withenvY . setup_sample:setupY start setupY . withenvY y stop setupY saving computed values ------------------------ Tasks can save computed values by returning a dictionary on it's python-actions. The values must be JSON encodable. A cmd-action can also save it's output. But for this you will need to explicitly import `CmdAction` and set its `save_out` parameter with the *name* used to save the output in *values* .. literalinclude:: samples/save_out.py These values can be used on uptodate_ and getargs_. Check those sections for examples. .. _getargs: getargs -------- `getargs` provides a way to use values computed from one task in another task. The values are taken from "saved computed values" (returned dict from a python-action). For *cmd-action* use dictionary-based string formatting. Formatting style is controlled by ``action_string_formatting`` key in ``DOIT_CONFIG`` (see :ref:`keywords on cmd-action string `). For *python-action* the action callable parameter names must match with keys from `getargs`. `getargs` is a dictionary where the key is the argument name used on actions, and the value is a tuple with 2 strings: task name, "value name". .. literalinclude:: samples/getargs.py The values are being passed on to a python-action you can pass the whole dict by specifying the value name as ``None``. .. literalinclude:: samples/getargs_dict.py If a group-task is used, the values from all its sub-tasks are passed as a dict. .. literalinclude:: samples/getargs_group.py .. note:: ``getargs`` creates an implicit setup-task. .. _attr-calc_dep: calculated-dependencies ------------------------ Calculation of dependencies might be an expensive operation, so not suitable to be done on load time by task-creators. For this situation it is better to delegate the calculation of dependencies to another task. The task calculating dependencies must have a python-action returning a dictionary with `file_dep`, `task_dep`, `uptodate` or another `calc_dep`. .. note:: An alternative way (and often easier) to have task attributes that rely on other tasks execution is to use :ref:`delayed tasks `. On the example below ``mod_deps`` prints on the screen all direct dependencies from a module. The dependencies itself are calculated on task ``get_dep`` (note: get_dep has a fake implementation where the results are taken from a dict). .. literalinclude:: samples/calc_dep.py doit-0.36.0/doc/dictionary.txt000066400000000000000000000100451423054503100162070ustar00rootroot00000000000000' '0 'a' 'actions' 'b' 'backend' 'c' 'cc 'changed' 'check 'choice' 'clean' 'cleanforget' 'command 'command' 'dep 'dependencies' 'display' 'doit 'doit' 'echo 'echo' 'edit' 'file 'files' 'input 'insert' 'java 'json' 'kbd 'kbd' 'library' 'link' 'main 'main' 'make' 'minversion' 'my 'myscript' 'name' 'notavalidchoice' 'params' 'result 'run 'setup' 'standard' 'strict' 'success 'targets' 'teardown' 'that' 'this' 'timestamp' 'title' 'values' 'verbosity' 'version' 'x' 'y' 0' 2to3 375F 3Amaster 3d 4s API ARG ATCI Atomwise BC3 BaseAction BaseFail Biomechanics Blog CFEngine CLI CMake CSV CamelCase CatchedException Cheminformatics CmdAction CmdAction's CmdActions CmdOption CmdParse Config ConsoleReporter Ctrl DSL Daniele DbmDB DelayedLoader DelayedTask Dembia DependencyError DevOps DoIt DoitCmdBase DoitCommand DoitMain ETC2 ETag Elasticsearch FPGA FileChangedChecker FooCmd FrankStain Fuseki GH Gliwinski Guo Heggø INI IPython IPython's InteractiveAction InvalidCommand JSON JSONCodec KeyError KeyboardInterrupt Kubernetes L485 Laperche LongRunning LookupDict MD5 MP3 Makefile Makefiles Metagenomics MetalK8s ModuleTaskLoader MyChecker MyCustomTask2 MyLoader Naufel OGG OpenCollective PCM PDB PVR PYTHONPATH Pagel Pandoc Popen PyPi PyPy PythonAction PythonInteractiveAction Pythonic README RPMs RSS ReST Repology S3 SCons Scality Schettino Segata SetupError SetupSample SkOink Sorenson StackOverflow Subclassing Sylvain TC4 TC5 TODO TOML TaskError TaskFailed TaskLoader TaskLoader2 TaskResult Tpng Trento USD UTF UnmetDependency UptodateCalculator Uz VCS WORDBREAKS Waf ZeroReporter a2 abc acyclic analytics api aplay app aren arg args atime attr auth auto1 autoclass autoload automethod aws b4 backend backends backport bar' basename basenames bdist bioinformatic bioinformatics biomechanics bitbucket blog bool boolean bountysource c' cProfile calc callables cd cfg changelog chdir cid cleanforget cloudpickle cls cmd cmp codebase codec codecs compFile compat compinit compressed1 conf config configparser cp cproject cron ctime customizable cwd dbm debian defs dep dep' deps deserialize dev dev0 devops dict dict's didn dir discoverable docstring dodoFile doit dryrun dumbdbm dumpdb efg encodable env eq executables faq file' file0 file1 file2 file3 fileno filepath flagoff fnmatch folderXXX foo foo' formatter formatters fpath functools gdbm genindex genstandalone getargs gif github gitter globals gmail google gprof2dot graphviz gz hardcode hashbang hashlib hello2 helloworld hg hggit hgrc hoc howmany html https hunspell img importlib ini init initializer inotify integrations interactiveaction internet intra io ipython isatty issuecomment iteritems iterkeys java js jsFile json json' kbd kwargs lelele letsdoit linters linux literalinclude longrunning lovin macfsevents makedirs maxdepth md5 md5sum metadata metavar microbiome minversion mkdir modindex mortem msg mtime multiprocess mycmd mygroup myscript mytask namespace nikola notavalidchoice o' once' online org os outfile param param1 param2 parametrization parametrize parametrizing params pathlib paypal pdb perl picklable pixelmap plugin plugins png popen pos pos1 pre preflight prem prev printf procida programmatically pstats py py' py3 pyFiles pychecker pycon pydoit pyflakes pygments pygraphviz pyinotify pylogo pypi pyproject pytest python2 python3 pythonic quickstart refactor regex repr rst2s5 runnable runtests runtime s' s3 s5defs scalable schettino schettino72 schettino72 scons sdist selecttasks serializable settrace setupX setupY setuptools shouldn shrinksafe shutils sourcecode sourceforge sqlite3 src startswith stateful stderr stdlib stdout str strace subclasses subcommand subprocess subtask subtasks sudo sys t1 t2 t3 t4vKPhjcMZg tabcompletion task1 task2 taskorder taskresult tasks' teardown time' timedelta timestamp titlewithactions to's toc toctree toml tomli tomlkit tomllib toolchain toolchains traceback tsetup tuto txt txt' ub ubuntu uio unhandled unicode unix unmaintained uptodate uptodate' usecases ut utf8 utils utm virtualenv visualisation waf walkthrough wget whl wildcard withenvX withenvY workdir workflow wxKa1h11zn x' xxx xyz zsh doit-0.36.0/doc/extending.rst000066400000000000000000000126741423054503100160320ustar00rootroot00000000000000.. meta:: :description: How to modify to core doit components, extend and create CLI programs :keywords: python, doit, documentation, guide, extending, framework, CLI .. title:: Using doit as a framework for CLI power-tools - pydoit guide ========================= Extending `doit` ========================= .. _extending: `doit` is built to be extended and this can be done in several levels. So far we have seen: 1) User's can create new ways to define when a task is up-to-date using the `uptodate` task parameter (:ref:`more `) 2) You can customize how tasks are executed by creating new Action types (:ref:`more `) 3) Tasks can be created in different styles by creating custom task creators (:ref:`more `) 4) The output can be configured by creating custom reports (:ref:`more `) Apart from those, `doit` also provides a plugin system and expose it's internal API so you can create new applications on top of `doit`. .. _custom_loader: task loader customization =========================== The task loader controls the source/creation of tasks. Normally `doit` tasks are defined in a `dodo.py` file. This file is loaded, and the list of tasks is created from the dict containing task meta-data from the *task-creator* functions. Subclass ``TaskLoader2`` to create a custom loader: .. autoclass:: doit.cmd_base.TaskLoader2 :members: Before the introduction of ``TaskLoader2`` a now deprecated loader interface ``TaskLoader`` was used, which did not separate the setup, configuration loading and task loading phases explicit. The main program is implemented in the `DoitMain`. It's constructor takes an instance of the task loader to be used. Example: pre-defined task ---------------------------- In the full example below a application is created where the only task available is defined using a dict (so no `dodo.py` will be used). .. literalinclude:: samples/custom_loader.py .. _ModuleTaskLoader: Example: load tasks from a module ------------------------------------- The `ModuleTaskLoader` can be used to load tasks from a specified module, where this module specifies tasks in the same way as in `dodo.py`. `ModuleTaskLoader` is included in `doit` source. .. literalinclude:: samples/module_loader.py `ModuleTaskLoader` can take also take a `dict` where its items are functions or methods of an object. .. _custom_command: command customization ===================== In `doit` a command usually perform some kind of operations on tasks. `run` to execute tasks, `list` to display available tasks, etc. Most of the time you should really be creating tasks but when developing a custom application on top of `doit` it may make sense to provide some extra commands... To create a new command, subclass `doit.cmd_base.Command` set some class variables and implement the `execute` method. .. autoclass:: doit.cmd_base.Command :members: execute ``cmd_options`` uses the same format as :ref:`task parameters `. If the command needs to access tasks it should sub-class `doit.cmd_base.DoitCmdBase`. Example: scaffolding ---------------------- A common example is applications that provide some kind of scaffolding when creating new projects. .. literalinclude:: samples/custom_cmd.py .. _plugins: plugins ======= `doit` plugin system is based on the use of *entry points*, the plugin does not need to implement any kind of "plugin interface". It needs only to implement the API of the component it is extending. Plugins can be enabled in 2 different ways: - *local plugins* are enabled through the `doit.cfg` file. - plugins installed with *setuptools* (that provide an entry point), are automatically enabled on installation. Check this `sample plugin `_ for details on how to create a plugin. config plugin ------------- To enable a plugin create a section named after the plugin category. The value is an entry point to the python class/function/object that implements the plugin. The format is `:`. Example of command plugin configured in `pyproject.toml`, implemented in the *class* `FooCmd`, located at the module `my_plugins.py`: .. code-block:: toml [tool.doit.plugins.command] foo = "my_plugins:FooCmd" Similarly, in `doit.cfg`: .. code-block:: ini [COMMAND] foo = my_plugins:FooCmd .. note:: The python module containing the plugin must be in the *PYTHONPATH*. category COMMAND ---------------- Creates a new sub-command. Check :ref:`command ` section for details on how to create a new command. category BACKEND ---------------- Implements the internal `doit` DB storage system. Check the module `doit/dependency.py` to see the existing implementation / API. .. _plugin_reporter: category REPORTER ----------------- Register a custom reporter as introduced in the :ref:`custom reporter` section. category LOADER ---------------- Creates a custom task loader. Check :ref:`loader ` section for details on how to create a new command. Apart from getting the plugin you also need to indicate which loader will be used. In the `tool.doit` section in `pyproject.toml`: .. code-block:: toml [tool.doit] loader = "my_loader" [tool.doit.plugins.loader] my_loader = "my_plugins:Loader" In the `GLOBAL` section of `doit.cfg`: .. code-block:: ini [GLOBAL] loader = my_loader [LOADER] my_loader = my_plugins:MyLoader doit-0.36.0/doc/faq.rst000066400000000000000000000045721423054503100146120ustar00rootroot00000000000000.. meta:: :description: Frequently Asked Questions on doit design and usage :keywords: python, doit, faq .. title:: FAQ - Frequently Asked Questions on doit design and usage ======= FAQ ======= Why is `doit` written in all lowercase instead of CamelCase? ------------------------------------------------------------- At first it would be written in CamelCase `DoIt` but depending on the font some people would read it as `dolt `_ with an `L` instead of `I`. So I just set it as lowercase to avoid confusion. *doit* is too verbose, why don't you use decorators? ----------------------------------------------------- `doit` is designed to be extensible. A simple dictionary is actually the most flexible representation. It is possible to create different interfaces on top of it. Check this `blog post `_ for some examples. `dodo.py` file itself should be a `file_dep` for all tasks ----------------------------------------------------------- If I edit my `dodo.py` file and re-run *doit*, and my tasks are otherwise up-to-date, the modified tasks are not re-run. While developing your tasks it is recommended to use ``doit forget`` after you change your tasks or use ``doit --always-run``. In case you really want, you will need to explicitly add the `dodo.py` in `file_dep` of your tasks manually. If `dodo.py` was an implicit `file_dep`: * how would you disable it? * should imported files from your `dodo.py` also be a `file_dep`? Why `file_dep` can not depend on a directory/folder? ------------------------------------------------------ A `file_dep` is considered to not be up-to-date when the content of the file changes. But what is a folder change? Some people expect it to be a change in any of its containing files (for this case see question below). Others expect it to be whether the folder exist or not, or if a new file was added or removed from the folder (for these cases you should implement a custom ``uptodate`` (:ref:`check the API`). How to make a dependency on all files in a folder? ---------------------------------------------------- ``file_dep`` does NOT support folders. If you want to specify all files from a folder you can use a third party library like `pathlib `_ ( `pathlib` was add on python's 3.4 stdlib). doit-0.36.0/doc/globals.rst000066400000000000000000000046571423054503100154720ustar00rootroot00000000000000.. meta:: :description: Accessing doit internal database :keywords: python, doit, documentation, guide, database, status .. title:: Accessing doit internal database - pydoit guide ================================== Globals - accessing doit internals ================================== During the life-cycle of a command invocation, some properties of `doit` are stored in global singletons, provided by the ``doit.Globals`` class. .. autoclass:: doit.globals.Globals :members: dep_manager ----------- The doit dependency manager holds the persistent state of `doit`. This includes relations of tasks among each other or results, returned from their latest runs. It basically consists of all the information stored in `doit`'s database file. The ``dep_manager`` attribute is initialized right before tasks are loaded, which means it allows to be accessed during *all* task evaluation phases, in particular during: * Task creation, i.e. from the body of any ``task_*`` function. * Task execution, i.e. from the code executed by one of the task's actions. * Task cleanup, i.e. from the the code executed by one of the tasks's clean activities. The `Dependency` class has members to access persistent doit data via its API: .. autoclass:: doit.dependency.Dependency :members: get_values, get_value, get_result The class internally interacts with a data base backend which may be accessed via the `backend` attribute. An experienced user may also *modify* persistently stored *doit* data through that attribute. As an example of a backend API, look at the common methods exposed by the default `DbmDB` backend implementation: .. class:: doit.dependency.DbmDB A simple Key-Value database interface .. automethod:: doit.dependency.DbmDB.get .. automethod:: doit.dependency.DbmDB.set .. automethod:: doit.dependency.DbmDB.remove There are other backends available in *doit*, see the documentation on :ref:`db_backends` on how to select between them. ``dep_manager`` example +++++++++++++++++++++++ An example of using the exposed dependency manager is a task, where at creation time the *target* of the task action is not yet known, because it is determined during execution. Then it would be possible to store that target in the dependency manager by returning it from the action. A `clean` action is subsequently able to query `dep_manager` for that result and perform the cleanup action: .. literalinclude:: samples/global_dep_manager.py doit-0.36.0/doc/google726fc03ab55ebbfc.html000066400000000000000000000000651423054503100200160ustar00rootroot00000000000000google-site-verification: google726fc03ab55ebbfc.htmldoit-0.36.0/doc/index.html000066400000000000000000001013411423054503100152760ustar00rootroot00000000000000 pydoit - Task Runner - Python CLI Tool

doit comes from the idea of bringing the power of build-tools to execute any kind of task


python logo
pip install doit

People often compare doit to tools like make, grunt, rake, scons, snakemake.

They appreciate doit strong features, flexibility, simplicity of authoring and ease of use.

Sample Code

Define functions returning python dict with task's meta-data.

Snippet from tutorial.

def task_imports():
    """find imports from a python module"""
    for name, module in PKG_MODULES.by_name.items():
        yield {
            'name': name,
            'file_dep': [module.path],
            'actions': [(get_imports, (PKG_MODULES, module.path))],
        }

def task_dot():
    """generate a graphviz's dot graph from module imports"""
    return {
        'targets': ['requests.dot'],
        'actions': [module_to_dot],
        'getargs': {'imports': ('imports', 'modules')},
        'clean': True,
    }

def task_draw():
    """generate image from a dot file"""
    return {
        'file_dep': ['requests.dot'],
        'targets': ['requests.png'],
        'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'],
        'clean': True,
    }
$ doit list
dot       generate a graphviz's dot graph from module imports
draw      generate image from a dot file
imports   find imports from a python module
$ doit
.  imports:requests.models
.  imports:requests.__init__
.  imports:requests.help
(...)
.  dot
.  draw


Task Runner

doit allows you to easily define ad-hoc tasks, helping you to organize all your project related tasks in an unified easy-to-use & discoverable way.

python powered

doit uses plain python to define tasks.

Task's meta-data are better described in a declarative way, but often you want to create this meta-data programmatically.

easy authoring

NO API: Tasks are described by a python dict (can also be easily customized)

Tasks can execute external process (shell commands) or python code

debugger & self documented

Since plain python is used to define your tasks the python debugger pdb is available

doit command allows you to list and obtain help/documentation for tasks


Build tool & Pipelines

Simple task runners simply do not scale-up. doit as other build-tools can be much more efficient at repeateadly running tasks.

cache task results
aka incremental-builds

doit creates a DAG and ensures that only required tasks will be executed and in the correct order.

doit checks if the task is up-to-date and skips its execution if the task would produce the same result of a previous execution.

up-to-date check

dependencies can be dynamically calculated by other tasks

the up-to-date check to cache task results is not restricted to looking for file modification on dependencies. Nor requires target files.

Pipelines

Traditional build-tools were created mainly to deal with compile/link process of source code. doit was designed to solve a broader range of workflows.

results from a task can be used by another task without resorting to the creation of intermediate files


Advanced Features

Extensible

Custom output
Command line output can be completely customized through reporters

Plugins
allow you to create/modify sub-commands, storage backend, task loader, and output reporter

Framework
API is exposed so you can create new applications/tools leveraging doit functionality

Batteries included

parallel execution
built-in support for parallel (threaded or multi-process) task execution

watch/auto execution
built-in support watching for file changes and automatically re-execute tasks based on file changes by external process [linux/mac only]

tab-completion
built-in support tab-completion for commands/task (supports bash and zsh)

DAG Visualisation
create task's dependency-graph image using graphviz

IPython
IPython integration provide %doit magic function that loads tasks defined directly in IPython's global namespace

strace
Integration with strace helps you understand effects of third-part commands

Testimonials

Status

doit is under active development. Version 0.36.0 released on 2022-04.

doit runs on Python 3.8 through 3.10 (including PyPy). For python 2 support please use doit version 0.29.

This blog post explains how everything started in 2008.

doit core features are quite stable. If there is no recent development, it does NOT mean the project is not being maintained... The project has 100% unit-test code coverage.

Development is done based on real world use cases. It is well designed and has a small code base, so adding new features is not hard. Contributions are welcome.

Project Details

LICENSE
This is an open-source project (MIT license) written in python.
DOWNLOAD
from PyPi
SUPPORT
See support page
DEVELOP
Project management (bug tracker, feature requests and source code ) on github
WEBSITE
This web site is hosted on http://pages.github.com
Powered by Universal template and Sphinx.
doit-0.36.0/doc/install.rst000066400000000000000000000020071423054503100155000ustar00rootroot00000000000000.. meta:: :description: How-to install pydoit using pip, from source or OS package :keywords: python, doit, install, git, github, ubuntu, linux, windows .. title:: pydoit package installation options ========== Installing ========== pip ^^^ `package `_:: $ pip install doit Latest version of `doit` supports only python 3. If you are using python 2:: $ pip install "doit<0.30" Source ^^^^^^ Download `source `_:: $ pip install -e . git repository ^^^^^^^^^^^^^^ Get latest development version:: $ git clone https://github.com/pydoit/doit.git OS package ^^^^^^^^^^ Several distributions include native `doit` packages. `Repology.org `_ provides up-to-date information about available packages and `doit` versions on each distribution. Anaconda ^^^^^^^^ `doit` is also packaged on `Anaconda `_. Note this is not an official package and might be outdated. doit-0.36.0/doc/make.bat000066400000000000000000000014541423054503100147120ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build set SPHINXPROJ=doit if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd doit-0.36.0/doc/old/000077500000000000000000000000001423054503100140575ustar00rootroot00000000000000doit-0.36.0/doc/old/index_old.rst000066400000000000000000000204741423054503100165650ustar00rootroot00000000000000:orphan: .. rubric:: `doit` is a task management & automation tool .. rubric:: `doit` comes from the idea of bringing the power of build-tools to execute any kind of **task** `doit` is a modern open-source build-tool written in python designed to be simple to use and flexible to deal with complex work-flows. It is specially suitable for building and managing custom work-flows where there is no out-of-the-box solution available. `doit` has been successfully used on: systems test/integration automation, scientific computational pipelines, content generation, configuration management, etc. Check some `success stories `_ ... introduction ============ A **task** describes some computation to be done (*actions*), and contains some extra meta-data. .. code-block:: python def task_example(): return { 'actions': ['myscript'], 'file_dep': ['my_input_file'], 'targets': ['result_file'], } **actions**: - can be external programs (executed as shell commands) or python functions. - a single task may define more than one action. **task meta-data**: - task meta-data includes a description of input file (**dependencies**) for the *actions* and result files (**targets**) - there are many other meta-data fields to control how and when a task is executed... *doit* uses the task's meta-data to: .. topic:: cache task results (aka *incremental-builds*) `doit` checks if the task is **up-to-date** and skips its execution if the task would produce the same result of a previous execution. .. topic:: correct execution order By checking the inter-dependency between tasks `doit` ensures that tasks will be execute in the correct order. .. topic:: parallel execution built-in support for parallel (threaded or multi-process) task execution (:ref:`more `) Traditional build-tools were created mainly to deal with compile/link process of source code. `doit` was designed to solve a broader range of tasks. .. topic:: powerful dependency system - the *up-to-date* check is not restricted to looking for file modification on dependencies, it can be customized for each task (:ref:`more `) - *target* files are not required in order to check if a task is up-to-date (:ref:`more `) - *dependencies* can be dynamically calculated by other tasks (:ref:`more `) Task's metadata (actions, dependencies, targets...) are better described in a declarative way, but often you want to create this metadata programmatically. .. topic:: flexible task definition `doit` uses plain python modules to create tasks (and its meta-data) .. topic:: customizable task definition By default tasks are described by a python `dict`. But it can be easily customized. (:ref:`more `) .. topic:: debugger Since plain python is used to define your tasks the python debugger (`pdb`) is available as in any other python application Other features... .. topic:: self documented `doit` command allows you to list and obtain help/documentation for tasks (:ref:`more `) .. topic:: inotify integration built-in support for a long-running process that automatically re-execute tasks based on file changes by external process (linux/mac only) (:ref:`more `) .. topic:: custom output process output can be completely customized through *reporters* (:ref:`more `) .. topic:: tab-completion built-in support tab-completion for commands/task (supports bash and zsh) (:ref:`more `) .. topic:: IPython integration provide `%doit` magic function that loads tasks defined directly in IPython's global namespace (:ref:`more `) .. topic:: extensible Apart from using `doit` to automate your project it also expose its API so you can create new applications/tools using `doit` functionality (:ref:`more `) Check the `documentation `_ for more features... What people are saying about `doit` ===================================== Congratulations! **Your tool follows the KISS principle very closely**. I always wondered why build tools had to be that complicated. - `Elena `_ Let me start by saying I'm really lovin doit, at first the interface seemed verbose but quickly changed my mind when **I started using it and realized the flexibility**. Many thanks for the great software! - `Michael Gliwinski `_ I love all the traditional unix power tools, like cron, make, perl, ..., I also like new comprehensive configuration management tools like CFEngine and Puppet. But **I find doit to be so versatile and so productive**. - `Charlie Guo `_ I went back and forth on different Pythonic build tools for awhile. Scons is pretty great if you're doing 'standard' sorts of builds, but I found it a little heavy for my tastes and really hard to customize to my tool flow (in FPGA land, there are all kinds of nonstandard vendor tools that all need to play together). I have been using doit more and more over the past few months, and I'm continually impressed by the tool (aside from the goofy name). **It works amazingly well for automating tricky/exotic build processes**. Check it out! `SkOink `_ I needed a sort of 'make' tool to glue things together and after trying out all kinds, **doit ... has actually turned out to be beautiful**. Its easy to add and manage tasks, even complex ones-- gluing things together with decorators and 'library' functions I have written to do certain similar things. - `Matthew `_ Some time ago, I grew frustrated with Make and Ant and started porting my build files to every build tool I found (SCons, Waf, etc.). Each time, as soon as I stepped out of already available rules, I ran into some difficult to overcome stumbling blocks. **Then I discovered this little gem of simplicity: doit**. It's Python-based. It does not try to be smart, it does not try to be cool, it just works. If you are looking for a flexible little build tool for different languages and tasks, give it a chance. (...) - `lelele `_ `Success Stories... `_ Project Details =============== * **LICENSE** This is an open-source project (`MIT license `_) written in python. Runs on Python 3.4 through 3.6 (including PyPy support). For python 2 support please use *doit* version 0.29. * **DOWNLOAD** from `PyPi `_ * **CONTRIBUTE** Please check the community `guidelines `_ before asking questions and reporting issues. * **DEVELOP** Project management (bug tracker, feature requests and source code ) on `github `_. * **QUESTIONS** on `StackOverflow `_, use tag `doit`. * **FORUM** (questions/feedback/discussion) on `Google group `_. Please do **not** send questions to my private email. * `doit projects `_ contains a collection of third-party projects, plugins, extensions, non-trivial examples and re-usable task creators for `doit`. * This web site is hosted on http://pages.github.com * Professional support and consulting services available from `doit` creator & maintainer (*schettino72* at gmail.com). Status ====== This blog `post `_ explains how everything started in 2008. `doit` is under active development. Version 0.31 released on 2018-02. `doit` core features are quite stable. If there is no recent development, it does NOT mean `doit` is not being maintained... The project has 100% unit-test code coverage. Development is done based on real world use cases. It is well designed and has a small code base, so adding new features is not hard. Contributions are welcome. doit-0.36.0/doc/open_collective.md000066400000000000000000000053521423054503100170020ustar00rootroot00000000000000the tool for **stateful processing of your interdependent tasks** powered by: - nuclear power-plant within your **shell** - **python** batteries - **doit** processing engine doit is mature project started in 2008 by @schettino72 and maintained by him up to date. It already serves years in numerous projects to: - simplify cumbersome command line calls, - automate complex data processing or typical project related actions, - share unified way of doing things, - optimize processing time by skipping things already done. People often compare `doit` to tools like `make`, `grunt` or `gulp` but they always appreciate - strong features and flexibility - simplicity of authoring and ease of use - python # Current focus ## Maintain existing code-base The aim is to **keep the product in shape and usable**. Abandoned open-source project does not work for long. Thriving project requires a maintainer to keep list of issues and list of pull requests short. Financial goal is 500 USD per month to allow the maintainer working few hours a week on the project. Main capacity shall be provided by @schettino72 # Long term vision ## B: Rewrite documentation The aim is to **lower initial learning barrier for newcomers to get them on board** and to **help pro-users to unlock more features and earn fame**. Existing documentation is good as it served well existing users. But we can serve better. Inspired by great talk [What nobody tells you about documentation](https://www.youtube.com/watch?v=t4vKPhjcMZg&t=4s) by Daniele Procida we plan to rewrite the documentation into following parts: - Introduction (basic features overview) - Quick start and tutorials - Reference documentation - Concepts - How-to's ## C: Promote We believe, many more users deserve `doit` and we shall help them to know about it. Promotion may have form of: - helping selected python projects to adopt doit as internal tool - video presentations - presentation(s) at pycon(s) ## D: Launch doit task libraries Grunt library has over 6000 tasks, gulp has over 3000 plug-ins. `doit` has similar potential. E.g. [doit-py](https://github.com/pydoit/doit-py) covers python code specific tasks. Few lines of code in [dodo.py](https://github.com/pydoit/doit-py/blob/master/dodo.py) to lint the code; run the tests and measure coverage; build and upload package; spell, build and publish sphinx based documentation. # Why to contribute? Because: - You can already use existing power of the tool. - The [github doit repository](https://github.com/pydoit/doit) shows stable, long term activity with small but existing community of secondary contributors. - You can see, we have clear plan and priorities. - You can improve your productivity with maintained tool and streamlined documentation. - You can doit. doit-0.36.0/doc/presentation.rst000066400000000000000000000124121423054503100165460ustar00rootroot00000000000000.. include:: ====================================================================== doit: bringing the power of build tools to execute any kind of task ====================================================================== "Anything worth repeating is worth automating" :Author: Eduardo Schettino build tools ============ Tools that manage repetitive tasks and their dependencies * 1977: Make * C & other compiled languages Make - how it works =================== * rules target: dependencies ... commands ... * simple (and fragile) dependency checking (timestamps) Make - how does it look? ======================== .. class:: tiny .. sourcecode:: make helloworld: helloworld.o cc -o $@ $< helloworld.o: helloworld.c cc -c -o $@ $< .PHONY: clean clean: rm -f helloworld helloworld.o Make - problems =============== * Make is not a programming language (how can I do a loop?) * compact syntax (but hard to understand and remember) * hard to debug other build tools ================== * CMake * java/XML: ant, maven * Rake * SCons dynamic languages ================= * who needs a build tool? * unit-test *web development* * heavy use of database * tests on browsers are slow * need environment setup (start servers, reset DB...) doit - design ============= * nice language => python * get out of your way * we-don't-need-no-stinking-API (learned from pytest) * dependencies by task not on file/targets (unique feature) doit - do what? ================ it's up to you! doit is a tool to help you execute **your** tasks in a efficient way. doit - how it works =================== * actions => what the task does * python (portable) * shell commands (fast, easy to use other programs) * targets => what this task creates * dependencies => what this task uses as input doit - how does it look? (1) ============================ .. class:: tiny .. sourcecode:: python DEFAULT_TASKS = ['edit'] # map source file to dependencies SOURCE = { 'main': ["defs.h"], 'kbd': ["defs.h command.h"], 'command': ["defs.h command.h"], 'display': ["defs.h buffer.h"], 'insert': ["defs.h buffer.h"], 'files': ["defs.h buffer.h command.h"], } OBJECTS = ["%s.o" module for module in SOURCE.iterkeys()] doit - how does it look? (2) ============================ .. class:: tiny .. sourcecode:: python def task_edit(): return {'actions': ['cc -o edit %s' % " ".join(OBJECTS)], 'dependencies': OBJECTS, 'targets': ['edit'] } def task_object(): for module, dep in SOURCE.iteritems(): dependencies = dep + ['%s.c' % module] yield {'name': module, 'actions': ["cc -c %s.c" % module] 'targets': ["%s.o" % module], 'dependencies': dependencies, } doit - how does it look? (3) ============================ .. class:: tiny .. sourcecode:: python import os def task_clean(): for f in ['edit'] + OBJECTS: yield {'name': f, 'actions': [(os.remove, f)], 'dependencies': [f]} doit - no targets ================= .. class:: tiny .. sourcecode:: python import glob; pyFiles = glob.glob('*.py') def task_checker(): for f in pyFiles: yield {'actions': ["pychecker %s"% f], 'name':f, 'dependencies':(f,)} doit - run once =============== .. class:: tiny .. sourcecode:: python URL = "http://svn.dojotoolkit.org/src/util/trunk/shrinksafe/shrinksafe.jar" shrinksafe = "shrinksafe.jar" jsFile = "file1.js" compFile = "compressed1.js" def task_shrink(): return {'actions': ['java -jar %s %s > %s'% (shrinksafe, jsFile, compFile)], 'dependencies': [shrinksafe] } def task_get_shrinksafe(): return {'actions': ["wget %s"% URL], 'targets': [shrinksafe], 'dependencies': [True] } doit - groups ============= .. class:: tiny .. sourcecode:: python def task_foo(): return {'actions': ["echo foo"]} def task_bar(): return {'actions': ["echo bar"]} def task_mygroup(): return {'actions': None, 'dependencies': [':foo', ':bar']} doit - environment setup (1) ============================ .. class:: tiny .. sourcecode:: python ### task setup env. good for functional tests! class SetupSample(object): def __init__(self, server): self.server = server def setup(self): # start server pass def cleanup(self): # stop server pass doit - environment setup (2) ============================ .. class:: tiny .. sourcecode:: python setupX = SetupSample('x') setupY = SetupSample('y') def task_withenvX(): for fin in ('a','b','c'): yield {'name': fin, 'actions':['echo x'], 'setup': setupX} def task_withenvY(): return {'actions': ['echo x'], 'setup': setupY} doit - cmd line =============== * run * list * forget doit - future ============= * community > 1 * support clean task * command line parameters * specific support for common tasks (C compilation) * dependency scanners * speed improvements thanks =========== Questions? doit website: http://python-doit.sourceforge.net references: http://software-carpentry.org/ http://www.gnu.org/software/make/ presentation written in ReST/rst2s5 + pygments doit-0.36.0/doc/related.rst000066400000000000000000000013361423054503100154560ustar00rootroot00000000000000.. meta:: :description: Related Projects - task runner, build tool & pipelines :keywords: python, doit, task-runner, build-tool, pipeline, workflow .. title:: Related Projects - task runner, build tool & pipelines ================ Related Projects ================ These are the main build tools in use today. - `make `_ - `ant `_ - `SCons `_ - `Rake `_ There are `many `_ more... In this `post `_ I briefly explained my motivation to start another build tool like project. doit-0.36.0/doc/robots.txt000066400000000000000000000001421423054503100153470ustar00rootroot00000000000000User-agent: * Disallow: /_modules/ Disallow: /_sources/ Sitemap: https://pydoit.org/sitemap.xml doit-0.36.0/doc/samples/000077500000000000000000000000001423054503100147455ustar00rootroot00000000000000doit-0.36.0/doc/samples/.gitignore000066400000000000000000000001041423054503100167300ustar00rootroot00000000000000_build edit foo foo.txt hello.txt my_input.txt poo report.txt task1 doit-0.36.0/doc/samples/calc_dep.py000066400000000000000000000015201423054503100170470ustar00rootroot00000000000000DOIT_CONFIG = {'verbosity': 2} MOD_IMPORTS = {'a': ['b','c'], 'b': ['f','g'], 'c': [], 'f': ['a'], 'g': []} def print_deps(mod, dependencies): print("%s -> %s" % (mod, dependencies)) def task_mod_deps(): """task that depends on all direct imports""" for mod in MOD_IMPORTS.keys(): yield {'name': mod, 'actions': [(print_deps,(mod,))], 'file_dep': [mod], 'calc_dep': ["get_dep:%s" % mod], } def get_dep(mod): # fake implementation return {'file_dep': MOD_IMPORTS[mod]} def task_get_dep(): """get direct dependencies for each module""" for mod in MOD_IMPORTS.keys(): yield {'name': mod, 'actions':[(get_dep,[mod])], 'file_dep': [mod], } doit-0.36.0/doc/samples/check_timestamp_unchanged.py000066400000000000000000000007031423054503100224730ustar00rootroot00000000000000from doit.tools import check_timestamp_unchanged def task_create_foo(): return { 'actions': ['touch foo', 'chmod 750 foo'], 'targets': ['foo'], 'uptodate': [True], } def task_on_foo_changed(): # will execute if foo or its metadata is modified return { 'actions': ['echo foo modified'], 'task_dep': ['create_foo'], 'uptodate': [check_timestamp_unchanged('foo', 'ctime')], } doit-0.36.0/doc/samples/checker.py000066400000000000000000000001521423054503100167210ustar00rootroot00000000000000def task_checker(): return {'actions': ["pyflakes sample.py"], 'file_dep': ["sample.py"]} doit-0.36.0/doc/samples/clean_mix.py000066400000000000000000000003231423054503100172540ustar00rootroot00000000000000from doit.task import clean_targets def simple(): print("ok") def task_poo(): return { 'actions': ['touch poo'], 'targets': ['poo'], 'clean': [clean_targets, simple], } doit-0.36.0/doc/samples/cmd_actions.py000066400000000000000000000001731423054503100176030ustar00rootroot00000000000000def task_hello(): """hello cmd """ msg = 3 * "hi! " return { 'actions': ['echo %s ' % msg], } doit-0.36.0/doc/samples/cmd_actions_list.py000066400000000000000000000001401423054503100206300ustar00rootroot00000000000000def task_python_version(): return { 'actions': [['python', '--version']] } doit-0.36.0/doc/samples/cmd_from_callable.py000066400000000000000000000003441423054503100207250ustar00rootroot00000000000000from doit.action import CmdAction def task_hello(): """hello cmd """ def create_cmd_string(): return "echo hi" return { 'actions': [CmdAction(create_cmd_string)], 'verbosity': 2, } doit-0.36.0/doc/samples/command.c000066400000000000000000000000761423054503100165320ustar00rootroot00000000000000#include void command(int a){ printf("geez"); }; doit-0.36.0/doc/samples/command.h000066400000000000000000000000251423054503100165310ustar00rootroot00000000000000void command(int a); doit-0.36.0/doc/samples/compile.py000066400000000000000000000002331423054503100167450ustar00rootroot00000000000000def task_compile(): return {'actions': ["cc -c main.c"], 'file_dep': ["main.c", "defs.h"], 'targets': ["main.o"] } doit-0.36.0/doc/samples/compile_pathlib.py000066400000000000000000000007621423054503100204570ustar00rootroot00000000000000from pathlib import Path def task_compile(): working_directory = Path('.') # Path.glob returns an iterator so turn it into a list headers = list(working_directory.glob('*.h')) for source_file in working_directory.glob('*.c'): object_file = source_file.with_suffix('.o') yield { 'name': object_file.name, 'actions': [['cc', '-c', source_file]], 'file_dep': [source_file] + headers, 'targets': [object_file], } doit-0.36.0/doc/samples/config_params.py000066400000000000000000000003261423054503100201300ustar00rootroot00000000000000from doit.tools import config_changed option = "AB" def task_with_params(): return {'actions': ['echo %s' % option], 'uptodate': [config_changed(option)], 'verbosity': 2, } doit-0.36.0/doc/samples/cproject.py000066400000000000000000000017451423054503100171370ustar00rootroot00000000000000DOIT_CONFIG = {'default_tasks': ['link']} # map source file to dependencies SOURCE = { 'main': ["defs.h"], 'kbd': ["defs.h", "command.h"], 'command': ["defs.h", "command.h"], } def task_link(): "create binary program" OBJECTS = ["%s.o" % module for module in SOURCE.keys()] return {'actions': ['cc -o %(targets)s %(dependencies)s'], 'file_dep': OBJECTS, 'targets': ['edit'], 'clean': True } def task_compile(): "compile C files" for module, dep in SOURCE.items(): dependencies = dep + ['%s.c' % module] yield {'name': module, 'actions': ["cc -c %s.c" % module], 'targets': ["%s.o" % module], 'file_dep': dependencies, 'clean': True } def task_install(): "install" return {'actions': ['echo install comes here...'], 'task_dep': ['link'], 'doc': 'install executable (TODO)' } doit-0.36.0/doc/samples/custom_clean.py000066400000000000000000000003471423054503100177770ustar00rootroot00000000000000def my_cleaner(dryrun): if dryrun: print('dryrun, dont really execute') return print('execute cleaner...') def task_sample(): return { "actions" : None, "clean" : [my_cleaner], } doit-0.36.0/doc/samples/custom_cmd.py000066400000000000000000000005211423054503100174520ustar00rootroot00000000000000from doit.cmd_base import Command class Init(Command): doc_purpose = 'create a project scaffolding' doc_usage = '' doc_description = """This is a multiline command description. It will be displayed on `doit help init`""" def execute(self, opt_values, pos_args): print("TODO: create some files for my project") doit-0.36.0/doc/samples/custom_loader.py000066400000000000000000000011401423054503100201530ustar00rootroot00000000000000#! /usr/bin/env python3 import sys from doit.task import dict_to_task from doit.cmd_base import TaskLoader2 from doit.doit_cmd import DoitMain my_builtin_task = { 'name': 'sample_task', 'actions': ['echo hello from built in'], 'doc': 'sample doc', } class MyLoader(TaskLoader2): def setup(self, opt_values): pass def load_doit_config(self): return {'verbosity': 2} def load_tasks(self, cmd, pos_args): task_list = [dict_to_task(my_builtin_task)] return task_list if __name__ == "__main__": sys.exit(DoitMain(MyLoader()).run(sys.argv[1:])) doit-0.36.0/doc/samples/custom_reporter.py000066400000000000000000000005631423054503100205570ustar00rootroot00000000000000from doit.reporter import ConsoleReporter class MyReporter(ConsoleReporter): def execute_task(self, task): self.outstream.write('MyReporter --> %s\n' % task.title()) DOIT_CONFIG = {'reporter': MyReporter, 'verbosity': 2} def task_sample(): for x in range(3): yield {'name': str(x), 'actions': ['echo out %d' % x]} doit-0.36.0/doc/samples/custom_task_def.py000066400000000000000000000003411423054503100204670ustar00rootroot00000000000000def make_task(func): """make decorated function a task-creator""" func.create_doit_tasks = func return func @make_task def sample(): return { 'verbosity': 2, 'actions': ['echo hi'], } doit-0.36.0/doc/samples/defs.h000066400000000000000000000000311423054503100160310ustar00rootroot00000000000000 static int SIZE = 20; doit-0.36.0/doc/samples/delayed.py000066400000000000000000000011131423054503100167220ustar00rootroot00000000000000import glob from doit import create_after @create_after(executed='early', target_regex='.*\.out') def task_build(): for inf in glob.glob('*.in'): yield { 'name': inf, 'actions': ['cp %(dependencies)s %(targets)s'], 'file_dep': [inf], 'targets': [inf[:-3] + '.out'], 'clean': True, } def task_early(): """a task that create some files...""" inter_files = ('a.in', 'b.in', 'c.in') return { 'actions': ['touch %(targets)s'], 'targets': inter_files, 'clean': True, } doit-0.36.0/doc/samples/delayed_creates.py000066400000000000000000000005371423054503100204410ustar00rootroot00000000000000import sys from doit import create_after def say_hello(your_name): sys.stderr.write("Hello from {}!\n".format(your_name)) def task_a(): return { "actions": [ (say_hello, ["a"]) ] } @create_after("a", creates=['b']) def task_another_task(): return { "basename": "b", "actions": [ (say_hello, ["b"]) ], } doit-0.36.0/doc/samples/doit_config.py000066400000000000000000000002001423054503100175730ustar00rootroot00000000000000DOIT_CONFIG = {'default_tasks': ['my_task_1', 'my_task_2'], 'continue': True, 'reporter': 'json'} doit-0.36.0/doc/samples/download.py000066400000000000000000000003651423054503100171320ustar00rootroot00000000000000from doit.tools import run_once def task_get_pylogo(): url = "http://python.org/images/python-logo.gif" return {'actions': ["wget %s" % url], 'targets': ["python-logo.gif"], 'uptodate': [run_once], } doit-0.36.0/doc/samples/empty_subtasks.py000066400000000000000000000006171423054503100204000ustar00rootroot00000000000000import glob def task_xxx(): """my doc""" LIST = glob.glob('*.xyz') # might be empty yield { 'basename': 'do_x', 'name': None, 'doc': 'docs for X', 'watch': ['.'], } for item in LIST: yield { 'basename': 'do_x', 'name': item, 'actions': ['echo %s' % item], 'verbosity': 2, } doit-0.36.0/doc/samples/executable.py000077500000000000000000000002751423054503100174470ustar00rootroot00000000000000#! /usr/bin/env python3 def task_echo(): return { 'actions': ['echo hi'], 'verbosity': 2, } if __name__ == '__main__': import doit doit.run(globals()) doit-0.36.0/doc/samples/folder.py000066400000000000000000000003641423054503100165750ustar00rootroot00000000000000from doit.tools import create_folder BUILD_PATH = "_build" def task_build(): return {'actions': [(create_folder, [BUILD_PATH]), 'touch %(targets)s'], 'targets': ["%s/file.o" % BUILD_PATH] } doit-0.36.0/doc/samples/get_var.py000066400000000000000000000002551423054503100167500ustar00rootroot00000000000000from doit import get_var config = {"abc": get_var('abc', 'NO')} def task_echo(): return {'actions': ['echo hi %s' % config], 'verbosity': 2, } doit-0.36.0/doc/samples/getargs.py000066400000000000000000000014131423054503100167520ustar00rootroot00000000000000DOIT_CONFIG = { 'default_tasks': ['use_cmd', 'use_python'], 'action_string_formatting': 'both', } def task_compute(): def comp(): return {'x':5,'y':10, 'z': 20} return {'actions': [(comp,)]} def task_use_cmd(): return {'actions': ['echo x={x}', # new-style formatting 'echo z=%(z)s'], # old-style formatting 'getargs': {'x': ('compute', 'x'), 'z': ('compute', 'z')}, 'verbosity': 2, } def task_use_python(): return {'actions': [show_getargs], 'getargs': {'x': ('compute', 'x'), 'y': ('compute', 'z')}, 'verbosity': 2, } def show_getargs(x, y): print("this is x: {}".format(x)) print("this is y: {}".format(y)) doit-0.36.0/doc/samples/getargs_dict.py000066400000000000000000000004551423054503100177620ustar00rootroot00000000000000def task_compute(): def comp(): return {'x':5,'y':10, 'z': 20} return {'actions': [(comp,)]} def show_getargs(values): print(values) def task_args_dict(): return {'actions': [show_getargs], 'getargs': {'values': ('compute', None)}, 'verbosity': 2, } doit-0.36.0/doc/samples/getargs_group.py000066400000000000000000000007041423054503100201700ustar00rootroot00000000000000def task_compute(): def comp(x): return {'x':x} yield {'name': '5', 'actions': [ (comp, [5]) ] } yield {'name': '7', 'actions': [ (comp, [7]) ] } def show_getargs(values): print(values) assert sum(v['x'] for v in values.values()) == 12 def task_args_dict(): return {'actions': [show_getargs], 'getargs': {'values': ('compute', None)}, 'verbosity': 2, } doit-0.36.0/doc/samples/global_dep_manager.py000066400000000000000000000013161423054503100211020ustar00rootroot00000000000000import doit DOIT_CONFIG = dict( verbosity=2, ) def task_create(): # dependency manager is defined for all code inside the generator: dep_manager = doit.Globals.dep_manager def action(): # assume some involved logic to define ident: ident = 42 print('Created', ident) # store for clean: return dict(created=ident) def clean(task): result = dep_manager.get_result(task.name) if result: ident = result['created'] print('Deleted', ident) # possibly forget the task, after it was cleaned: dep_manager.remove(task.name) return dict( actions=[action], clean=[clean], ) doit-0.36.0/doc/samples/group.py000066400000000000000000000003051423054503100164510ustar00rootroot00000000000000def task_foo(): return {'actions': ["echo foo"]} def task_bar(): return {'actions': ["echo bar"]} def task_mygroup(): return {'actions': None, 'task_dep': ['foo', 'bar']} doit-0.36.0/doc/samples/hello.py000066400000000000000000000004071423054503100164230ustar00rootroot00000000000000def task_hello(): """hello""" def python_hello(targets): with open(targets[0], "a") as output: output.write("Python says Hello World!!!\n") return { 'actions': [python_hello], 'targets': ["hello.txt"], } doit-0.36.0/doc/samples/import_tasks.py000066400000000000000000000003061423054503100200350ustar00rootroot00000000000000# import task_ functions from get_var import task_echo # import tasks with create_doit_tasks callable from custom_task_def import sample def task_hello(): return {'actions': ['echo hello']} doit-0.36.0/doc/samples/initial_workdir.py000066400000000000000000000012621423054503100205120ustar00rootroot00000000000000### README # Sample to test doit.get_initial_workdir # First create a folder named 'sub1'. # Invoking doit from the root folder will execute both tasks 'base' and 'sub1'. # Invoking 'doit -k' from path 'sub1' will execute only task 'sub1' ################## import os import doit DOIT_CONFIG = { 'verbosity': 2, 'default_tasks': None, # all by default } # change default tasks based on dir from where doit was run sub1_dir = os.path.join(os.path.dirname(__file__), 'sub1') if doit.get_initial_workdir() == sub1_dir: DOIT_CONFIG['default_tasks'] = ['sub1'] def task_base(): return {'actions': ['echo root']} def task_sub1(): return {'actions': ['echo sub1']} doit-0.36.0/doc/samples/kbd.c000066400000000000000000000000501423054503100156440ustar00rootroot00000000000000#include "defs.h" #include "command.h" doit-0.36.0/doc/samples/longrunning.py000066400000000000000000000001601423054503100176540ustar00rootroot00000000000000from doit.tools import LongRunning def task_top(): cmd = "top" return {'actions': [LongRunning(cmd)],} doit-0.36.0/doc/samples/main.c000066400000000000000000000001201423054503100160260ustar00rootroot00000000000000#include int main() { printf("\nHello World\n"); return 0; } doit-0.36.0/doc/samples/meta.py000066400000000000000000000002771423054503100162530ustar00rootroot00000000000000def who(task): print('my name is', task.name) print(task.targets) def task_x(): return { 'actions': [who], 'targets': ['asdf'], 'verbosity': 2, } doit-0.36.0/doc/samples/metadata.py000066400000000000000000000001671423054503100171030ustar00rootroot00000000000000def task_unittest(): return { 'actions': ['echo unit-test'], 'meta': {'tags': ['test']}, } doit-0.36.0/doc/samples/module_loader.py000066400000000000000000000004001423054503100201240ustar00rootroot00000000000000#! /usr/bin/env python3 import sys from doit.cmd_base import ModuleTaskLoader from doit.doit_cmd import DoitMain if __name__ == "__main__": import my_module_with_tasks sys.exit(DoitMain(ModuleTaskLoader(my_module_with_tasks)).run(sys.argv[1:])) doit-0.36.0/doc/samples/my_dodo.py000066400000000000000000000020001423054503100167410ustar00rootroot00000000000000 DOIT_CONFIG = {'verbosity': 2} TASKS_MODULE = __import__('my_tasks') def task_do(): # get functions that are tasks from module for name in dir(TASKS_MODULE): item = getattr(TASKS_MODULE, name) if not hasattr(item, 'task_metadata'): continue # get task metadata attached to the function metadata = item.task_metadata # get name of task from function name metadata['name'] = item.__name__ # *I* dont like the names file_dep, targets. So I use 'input', 'output' class Sentinel(object): pass input_ = metadata.pop('input', Sentinel) output_ = metadata.pop('output', Sentinel) args = [] if input_ != Sentinel: metadata['file_dep'] = input_ args.append(input_) if output_ != Sentinel: metadata['targets'] = output_ args.append(output_) # the action is the function iteself metadata['actions'] = [(item, args)] yield metadata doit-0.36.0/doc/samples/my_module_with_tasks.py000066400000000000000000000001541423054503100215510ustar00rootroot00000000000000 def task_sample(): return {'actions': ['echo hello from module loader'], 'verbosity': 2,} doit-0.36.0/doc/samples/my_tasks.py000066400000000000000000000013711423054503100171530ustar00rootroot00000000000000def task(*fn, **kwargs): # decorator without parameters if fn: function = fn[0] function.task_metadata = {} return function # decorator with parameters def wrap(function): function.task_metadata = kwargs return function return wrap @task def simple(): print("thats all folks") @task(output=['my_input.txt']) def pre(to_create): with open(to_create[0], 'w') as fp: fp.write('foo') @task(output=['out1.txt', 'out2.txt']) def create(to_be_created): print("I should create these files: %s" % " ".join(to_be_created)) @task(input=['my_input.txt'], output=['my_output_result.txt']) def process(in_, out_): print("processing %s" % in_[0]) print("creating %s" % out_[0]) doit-0.36.0/doc/samples/parameters.py000066400000000000000000000033041423054503100174620ustar00rootroot00000000000000def task_py_params(): def show_params(param1, param2): print(param1) print(5 + param2) return {'actions':[(show_params,)], 'params':[{'name':'param1', 'short':'p', 'default':'default value'}, {'name':'param2', 'long':'param2', 'type': int, 'default':0}], 'verbosity':2, } def task_py_params_list(): def print_a_list(list): for item in list: print(item) return {'actions':[(print_a_list,)], 'params':[{'name':'list', 'short':'l', 'long': 'list', 'type': list, 'default': [], 'help': 'Collect a list with multiple -l flags'}], 'verbosity':2, } def task_py_params_choice(): def print_choice(choice): print(choice) return {'actions':[(print_choice,)], 'params':[{'name':'choice', 'short':'c', 'long': 'choice', 'type': str, 'choices': (('this', ''), ('that', '')), 'default': 'this', 'help': 'Choose between this and that'}], 'verbosity':2,} def task_cmd_params(): return {'actions':["echo mycmd %(flag)s xxx"], 'params':[{'name':'flag', 'short':'f', 'long': 'flag', 'default': '', 'help': 'helpful message about this flag'}], 'verbosity': 2 } doit-0.36.0/doc/samples/parameters_inverse.py000066400000000000000000000005701423054503100212170ustar00rootroot00000000000000def task_with_flag(): def _task(flag): print("Flag {0}".format("On" if flag else "Off")) return { 'params': [{ 'name': 'flag', 'long': 'flagon', 'short': 'f', 'type': bool, 'default': True, 'inverse': 'flagoff'}], 'actions': [(_task, )], 'verbosity': 2 } doit-0.36.0/doc/samples/pos.py000066400000000000000000000007341423054503100161240ustar00rootroot00000000000000def task_pos_args(): def show_params(param1, pos): print('param1 is: {0}'.format(param1)) for index, pos_arg in enumerate(pos): print('positional-{0}: {1}'.format(index, pos_arg)) return {'actions':[(show_params,)], 'params':[{'name':'param1', 'short':'p', 'default':'default value'}, ], 'pos_arg': 'pos', 'verbosity': 2, } doit-0.36.0/doc/samples/report_deps.py000066400000000000000000000007371423054503100176540ustar00rootroot00000000000000DOIT_CONFIG = {'action_string_formatting': 'both'} def task_report_deps(): """ Report dependencies and changed dependencies to a file. """ return { 'file_dep': ['req.in', 'req-dev.in'], 'actions': [ # New style formatting 'echo D: {dependencies}, CH: {changed} > {targets}', # Old style formatting 'cat %(targets)s', ], 'targets': ['report.txt'], } doit-0.36.0/doc/samples/run_once.py000066400000000000000000000002551423054503100171310ustar00rootroot00000000000000 def run_once(task, values): def save_executed(): return {'run-once': True} task.value_savers.append(save_executed) return values.get('run-once', False) doit-0.36.0/doc/samples/sample.py000066400000000000000000000000171423054503100165760ustar00rootroot00000000000000print("hello") doit-0.36.0/doc/samples/save_out.py000066400000000000000000000002741423054503100171470ustar00rootroot00000000000000from doit.action import CmdAction def task_save_output(): return { 'actions': [CmdAction("echo x1", save_out='out')], } # The task values will contain: {'out': u'x1'} doit-0.36.0/doc/samples/selecttasks.py000066400000000000000000000004241423054503100176440ustar00rootroot00000000000000 DOIT_CONFIG = {'default_tasks': ['t3']} def task_t1(): return {'actions': ["touch task1"], 'targets': ['task1']} def task_t2(): return {'actions': ["echo task2"]} def task_t3(): return {'actions': ["echo task3"], 'file_dep': ['task1']} doit-0.36.0/doc/samples/settrace.py000066400000000000000000000002451423054503100171320ustar00rootroot00000000000000 def need_to_debug(): # some code here from doit import tools tools.set_trace() # more code def task_X(): return {'actions':[(need_to_debug,)]} doit-0.36.0/doc/samples/subtasks.py000066400000000000000000000002471423054503100171610ustar00rootroot00000000000000def task_create_file(): for i in range(3): filename = "file%d.txt" % i yield {'name': filename, 'actions': ["touch %s" % filename]} doit-0.36.0/doc/samples/tar.py000066400000000000000000000003371423054503100161100ustar00rootroot00000000000000def task_tar(): return {'actions': ["tar -cf foo.tar *"], 'task_dep':['version'], 'targets':['foo.tar']} def task_version(): return {'actions': ["hg tip --template '{rev}' > revision.txt"]} doit-0.36.0/doc/samples/task_doc.py000066400000000000000000000001441423054503100171050ustar00rootroot00000000000000def task_hello(): return { 'actions': ['echo hello'], 'doc': 'say hello', } doit-0.36.0/doc/samples/task_kwargs.py000077500000000000000000000005151423054503100176430ustar00rootroot00000000000000def func_with_args(arg_first, arg_second): print(arg_first) print(arg_second) return True def task_call_func(): return { 'actions': [(func_with_args, [], { 'arg_second': 'This is a second argument.', 'arg_first': 'This is a first argument.'}) ], 'verbosity': 2, } doit-0.36.0/doc/samples/task_name.py000066400000000000000000000003421423054503100172600ustar00rootroot00000000000000def task_hello(): """say hello""" return { 'actions': ['echo hello'] } def task_xxx(): """say hello again""" return { 'basename': 'hello2', 'actions': ['echo hello2'] } doit-0.36.0/doc/samples/task_reusable.py000066400000000000000000000003031423054503100201370ustar00rootroot00000000000000 def gen_many_tasks(): yield {'basename': 't1', 'actions': ['echo t1']} yield {'basename': 't2', 'actions': ['echo t2']} def task_all(): yield gen_many_tasks() doit-0.36.0/doc/samples/taskorder.py000066400000000000000000000003441423054503100173160ustar00rootroot00000000000000def task_modify(): return {'actions': ["echo bar > foo.txt"], 'file_dep': ["foo.txt"], } def task_create(): return {'actions': ["touch foo.txt"], 'targets': ["foo.txt"] } doit-0.36.0/doc/samples/taskresult.py000066400000000000000000000003531423054503100175210ustar00rootroot00000000000000from doit.tools import result_dep def task_version(): return {'actions': ["hg tip --template '{rev}:{node}'"]} def task_send_email(): return {'actions': ['echo "TODO: send an email"'], 'uptodate': [result_dep('version')]} doit-0.36.0/doc/samples/timeout.py000066400000000000000000000003561423054503100170110ustar00rootroot00000000000000import datetime from doit.tools import timeout def task_expire(): return { 'actions': ['echo test expire; date'], 'uptodate': [timeout(datetime.timedelta(minutes=5))], 'verbosity': 2, } doit-0.36.0/doc/samples/title.py000066400000000000000000000002411423054503100164350ustar00rootroot00000000000000 def show_cmd(task): return "executing... %s" % task.name def task_custom_display(): return {'actions':['echo abc efg'], 'title': show_cmd} doit-0.36.0/doc/samples/titlewithactions.py000066400000000000000000000002261423054503100207150ustar00rootroot00000000000000from doit.tools import title_with_actions def task_with_details(): return {'actions': ['echo abc 123'], 'title': title_with_actions} doit-0.36.0/doc/samples/touch.py000066400000000000000000000003501423054503100164370ustar00rootroot00000000000000def task_touch(): return { 'actions': ['touch foo.txt'], 'targets': ['foo.txt'], # force doit to always mark the task # as up-to-date (unless target removed) 'uptodate': [True], } doit-0.36.0/doc/samples/tsetup.py000066400000000000000000000013411423054503100166420ustar00rootroot00000000000000### task setup env. good for functional tests! DOIT_CONFIG = {'verbosity': 2, 'default_tasks': ['withenvX', 'withenvY']} def start(name): print("start %s" % name) def stop(name): print("stop %s" % name) def task_setup_sample(): for name in ('setupX', 'setupY'): yield {'name': name, 'actions': [(start, (name,))], 'teardown': [(stop, (name,))], } def task_withenvX(): for fin in ('a','b','c'): yield {'name': fin, 'actions':['echo x %s' % fin], 'setup': ['setup_sample:setupX'], } def task_withenvY(): return {'actions':['echo y'], 'setup': ['setup_sample:setupY'], } doit-0.36.0/doc/samples/tutorial_02.py000066400000000000000000000004241423054503100174630ustar00rootroot00000000000000def task_hello(): """hello py """ def python_hello(times, text, targets): with open(targets[0], "a") as output: output.write(times * text) return {'actions': [(python_hello, [3, "py!\n"])], 'targets': ["hello.txt"], } doit-0.36.0/doc/samples/uptodate_callable.py000066400000000000000000000004271423054503100207660ustar00rootroot00000000000000 def fake_get_value_from_db(): return 5 def check_outdated(): total = fake_get_value_from_db() return total > 10 def task_put_more_stuff_in_db(): def put_stuff(): pass return {'actions': [put_stuff], 'uptodate': [check_outdated], } doit-0.36.0/doc/samples/verbosity.py000066400000000000000000000001251423054503100173430ustar00rootroot00000000000000def task_print(): return {'actions': ['echo hello'], 'verbosity': 2} doit-0.36.0/doc/stories.rst000066400000000000000000000334571423054503100155370ustar00rootroot00000000000000.. meta:: :description: pydoit Success Stories on scientific pipelines, build system, content generation and DevOps :keywords: python, doit, case study, build system, content generation, devops, scientific pipelines .. title:: pydoit Success Stories: users testimonials Success Stories =============== Do you have a success story? Please share it! Send a pull-request on github describing your project, how `doit` is used, and why `doit` was chosen. .. contents:: :local: Scientific ---------- Biomechanics Lab / Stanford University, USA ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by `Christopher Dembia `_ (2014-04-03) I am a graduate student in a biomechanics lab that studies how humans coordinate their muscles in movements like walking or running. We record someone's motion using reflective motion capture markers and by recording the force their feet exert on the ground. We put this data into our software, which gives back an estimate of the force each muscle (92 muscles) was generating throughout the observed motion. In a typical study, we record about 100 walking motions. To analyze a single walking motion, we need to run 4 different executables in sequence. Each executable requires a handful of input files, and generates a handful of output files that a subsequent executable uses as input. So, a study entails about 1000 files, some of which contain raw data, but most of which are intermediate files (output of one executable and input to another executable). Typically, a researcher manages this workflow manually. However, that is prone to error, as a researcher may forget to properly modify all relevant files if an error is noticed in, for example, a raw data file. With `doit`, I am automating this workflow for my current study. This allows me to avoid errors and avoid unnecessary duplication of files. Most importantly, if I learn that I must modify something in a file that is an input toward the beginning of this workflow, `doit` will allow me to automatically update all my results without missing a step. I tried to do this with `Make` first. `Make` just was not made to do what I want. Also, my lab's software has python bindings, so my entire workflow can be in python. Also, the ability to script anything directly into the workflow is important, and `Make` can't do that. `CMake` was another option, but that's not general enough. `doit` is just completely generic, and the interface is simple yet very flexible. `Computational Metagenomics Lab `_ / University of Trento, Italy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by `Nicola Segata `_ (2015-01-20) My laboratory of Computational Metagenomics at University of Trento studies the human microbiome, i.e. the huge microbial diversity populating our body. Our analyses involve processing several thousands of microbial genomes (long sequences of DNA) with a series of computational steps (mostly written in python) on subset of those genomes. Genomes are organized in a hierarchical structure (the taxonomy) and many steps of our pipelines need to take into account these dependencies. `doit` is just the right way to do this. We actually even tried to implement something like this on our own, but we are now switching to `doit` because it has all the features we need and it is intuitive to use. We particularly love the possibility of specifying dependencies "on the fly". We are now thinking to convert all our pipelines to the `doit` format. Given the importance of `doit` for our research and its potential for bioinformatic pipelines we are happy to support this project scientifically (by citing it in our papers, mentioning it in our funding requests, etc). Thanks for developing `doit`, it's just wonderful for computational biology (and for many other tasks, of course, but this is our research field:)! `Cheminformatics data processing @ Atomwise `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by `Jon Sorenson `_ (2018-12-14) I came to `doit` from the loose requirement that I wanted a task-dependency/DAG type of workflow solution for the various pipelines that our team is constructing. I come from computational biology originally, and the similarity of data processing pipelines to build systems has long been appreciated. More than a decade ago many of us were writing bioinformatics pipelines in `make` because it gave us so many features "for free." Starting afresh at `Atomwise `_ I did a survey of what DAG-based workflow execution frameworks were out there---restricting my search to `python`, active maintenance, good documentation etc. Besides `doit` I evaluated `bonobo`, `Luigi`, and `airflow`. `bonobo` didn't fit my needs for dependency-based processing. `Luigi` and `airflow` are very nice, but I didn't have any particular need for distributed workflows and the heavier weight feel of these platforms. My favorite experience was `airflow` but it didn't have (obvious) support for re-entrant processing: running a pipeline from an intermediate stage. I knew that build-system based frameworks would do exactly what I wanted and not hand me too much cruft, and on that note I found `doit`. It's worked perfectly for my needs: - The order of tasks is derived from dependencies - Tasks can be executed in parallel if they don't depend on each other - Plenty of bells and whistles to relieve the pipeline developer from writing boilerplate code (easy task documentation, discovery of the pipeline state, easy process for re-doing tasks) - Very light-weight. In this sense `doit` is the perfect example of a UNIX-style tool: *do one thing and do it well*. `Luigi` and `airflow` are attractive, but they also suffer from kitchen-sink bloat. If you simply want a pythonic alternative to `Makefiles` or `bash` scripts, `doit` is great solution. - It's easy to build up a library of common tasks that can be reused by multiple `doit` pipelines. Build System ------------ `BMW `_ (Automotive) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by `Mike Pagel `_ (2019-02-06) We are responsible for the development of the next generation instrument cluster software at BMW. While we use CMake for the actual build of libraries and applications, we have learned in the past that you *can* do almost everything with ``CMake``, but probably you *shouldn’t*. ``CMake`` is optimized for all tasks around compiler toolchain control, but the language is somewhat special and functions and macros cannot easily be tested outside of a real build. This is where ``doit`` enters the stage: We use it for everything *but* compiling software, as a high level command line interface for the development teams (and the CI systems). These are some of the tasks we perform with ``doit``: - Downloading and installing tools. - Calling ``CMake`` for multiple compiler toolchains. - Driving various code analysis tools. - Reporting. - Packaging the software for later deployment to the car etc. - Checking if dependencies of the toolchain are outdated and creating automatic pull requests. Basically we implemented our complete high-level build control in ``doit``. The resulting framework is now used by us and our suppliers and supports a team over 100 developers. Since ``doit`` is written in Python, we have professional test frameworks, linters and code analyzers at hand, allowing for a thoroughly tested and well-designed platform for our build-systems and automation. Game Development ^^^^^^^^^^^^^^^^ by `@FrankStain `_ (2017-10-01) I'm professional game developer. Also, i support my own huge game framework written on C++. :) So, the large scalable build systems are the game building automation tools. It consists of game binary image builders for different platforms, including cross-compilation of source code and source code generation from some DSL schemes. Also it consists of resource generators, where a lot of resource types (dozens of types: textures, 3d objects and scene graphs, sounds, database and state machine raw data) have to pass through dozens of compilation steps. After all, such build system consists of dynamic testing tool, which makes some tests on build target before make it published for Draft usage, QA or Retail customers. And, yep, publishing/QA deployment also implemented as part of build system. Just imagine you need to read PNG into pixelmap, compress it into ETC2, ATCI, S3-TC5/BC3 and PVR-TC4, after what each of compressed texture should be placed into different resource pack, obfuscated and encrypted. And all is done by different tasks, because i can read textures even from database, zip-file or other pack and may not wish to compress it into some formats. Each sound should be loaded from PCM, converted into MP3 or OGG and linked with each sound mixer where it used, after what it also have to be placed at proper resource pack, obfuscated and encrypted. 3d location compilation process is about two hundreds tasks on just objects, not files. It's most complex resource pipeline in build system. `doit` is well designed tool for such purposes, i think. `MetalK8s @ Scality `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by `Sylvain Laperche `_ (2019-05-06) We use ``doit`` as the build system for `MetalK8s `_, a Kubernetes distribution with a focus on long-term on-prem deployments. ``doit``'s main role is to generate the MetalK8s ISO archive that should contain everything to allow offline installation and deployment of a Kubernetes cluster. This involves several tasks like downloading and/or building container images, building software packages (RPMs) from source, creating packages repositories, … We also use ``doit`` for others tasks, such as executing linting tools and spawning a local cluster using Vagrant. We wanted to move away from ``make`` because as complexity grows it becomes hard to maintain, evolve and debug. Given that almost everyone in our team is familiar with Python, we started to look for alternatives that are Python-based. We investigated ``Scons``, ``waf``, ``Invoke`` and ``doit``. ``Scons`` and ``waf`` were put aside because their main advantage is to hide the underlying complexity of compiling software in a portable way (which we aren't doing). However, running arbitrary shell commands was cumbersome. ``Invoke`` was pretty good at executing commands, but didn't have a good dependency tracking system: a task will always be re-executed even if its dependencies are unchanged, which was a deal-breaker. ``doit`` was chosen to replace our ``make``-based approach because of the following characteristics: - Easy to invoke external commands - Simple and flexible core concepts - Easily customizable (``uptodate`` API, ``clean`` attribute, …) - Extensive documentation - Actively maintained - Various useful features: JSON output, ``doit info`` to inspect dependencies, ``doit auto`` for automatically replaying tasks based on dependency changes… Content Generation ------------------ Nikola ^^^^^^ by `the Nikola team `_ `Nikola `_ is a Static Site and Blog Generator. `doit` is used to process all the tasks required for building the website (HTML files, indexes, RSS, copying files…). Use of `doit` makes Nikola unique: unlike other static site generators, Nikola regenerates only the files that were changed since last build (and not all files in the site!). ``nikola build``, the centerpiece of Nikola, is basically the usual ``doit run`` command. `doit` is what makes Nikola extremely fast, even for large sites. Only a handful of files actually *change* on a rebuild. Using the dependency architecture of `doit` (for files and configuration), we are able to rebuild only what is needed. Nikola is an `open-source `_ project with many users and contributors. Document Production ^^^^^^^^^^^^^^^^^^^ (2018-02-01) `Carve Systems `_ uses `doit` as the core automation tool for all of our document production. This customized tool based on Pandoc, Latex, and coordinated by `doit` is used by everyone in our company to prepare our primary customer facing deliverable. Previously we used Makefiles to coordinate builds. `doit` let us create a system that can be more easily maintained, tested, and extended using plugins. DevOps ------ University of Oslo Library, Norway ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ by_ `Dan Michael O. Heggø `_ (2018-02-26) .. _by: #https-data-ub-uio-no We are using `doit` for the publishing workflow at our vocabulary server https://data.ub.uio.no/ . The server checks multiple remote sources for changes, and when there’s new changes somewhere, the data is fetched, converted to different formats, published and pushed to Fuseki and Elasticsearch. One part I love about `doit` is that you can control what is considered a change. For remote files, I have created a task that checks if some header, like ETag or Last-Modified, has changed. If it has not, I set `uptodate` to True and stop there. Another part I love is the ability to re-use tasks. Each vocabulary (like https://github.com/realfagstermer/realfagstermer and https://github.com/scriptotek/humord) has a different publication workflow, but many tasks are shared. With `doit`, I have created a collection of tasks and task generators (https://github.com/scriptotek/data_ub_tasks/) that I use with all the vocabularies. Finally, it's great that you can mix shell commands and Python tasks so easily. This cuts development time and makes the move from using Makefiles much easier. doit-0.36.0/doc/support.rst000066400000000000000000000050111423054503100155440ustar00rootroot00000000000000.. meta:: :description: doit community & getting help/support :keywords: python, doit, question, support, feature request, donations .. title:: doit community & getting help/support ======= Support ======= *doit* has extensive online documentation please read the docs! Feedback & Ideas ---------------- `Discussion forum `_ It is always nice to receive feedback. If you use *doit* please drop us line sharing your experience. It is appreciated if you introduce yourself, also mention how long are you using *doit* and using it for what. A lot of *doit* features were driven by external ideas and use-cases, so new ideas are welcome too. Questions --------- Question can be asked in the `discussion forum `_ or on `StackOverflow using tag doit `_. Please, do not use the github issue tracker to ask questions! Nor send private emails to project maintainer. Issues/bugs ----------- If you find issues using *doit* please report at `github issues `_. All issues should contain a sample minimal ``dodo.py`` and the used command line to reproduce the problem. Feature requests ---------------- Feature requests should also be raised on `github `_, sometimes it is a good idea to have an initial discussion of a new feature in the discussion forum. You will be expected provide a real world / example of why and how this feature would be used (if not obvious). Users are expected to implement new features themselves. If you are not willing to spend your time on it, probably nobody else will... You might sponsor a feature by placing `bounties `_ or by contacting the maintainer directly. Donations --------- If you consider **doit** to be a useful project, and would like to see it continue to be maintained and developed, please consider helping cover the development cost. Thriving project requires a maintainer to keep list of issues and list of pull requests short. **doit** is 100% open-source and maintained by individuals without any company backing it... financial help is appreciated. The project receives donation through `OpenCollective `_ or Paypal. Commercial Support ------------------ For commercial support as well as consulting on computational pipelines by `doit` creator & maintainer, please contact: *schettino72* at gmail.com. doit-0.36.0/doc/svg/000077500000000000000000000000001423054503100141005ustar00rootroot00000000000000doit-0.36.0/doc/svg/doit-sq.svg000066400000000000000000000263561423054503100162150ustar00rootroot00000000000000 image/svg+xml doit-0.36.0/doc/svg/doit-text-full.svg000066400000000000000000000325641423054503100175140ustar00rootroot00000000000000 image/svg+xml   Automation Tool   doit-0.36.0/doc/svg/doit-text-sq.svg000077500000000000000000000305001423054503100171640ustar00rootroot00000000000000 image/svg+xml doit-0.36.0/doc/svg/doit-text.svg000077500000000000000000000300371423054503100165500ustar00rootroot00000000000000 image/svg+xml doit-0.36.0/doc/svg/doit.svg000077500000000000000000000127731423054503100155750ustar00rootroot00000000000000 image/svg+xml doit-0.36.0/doc/task-args.rst000066400000000000000000000145111423054503100157310ustar00rootroot00000000000000.. meta:: :description: Parametrizing tasks with command line arguments and environment variables. :keywords: python, doit, documentation, guide, parametrization, task_param .. title:: Task parametrization, getting arguments from command line Passing arguments from the command line ======================================= .. _parameters: Task action parameters ---------------------- It is possible to pass option parameters to the task action through the command line. Just add a ``params`` field to the task dictionary. ``params`` must be a list of dictionaries where every entry is an option parameter. Each parameter must define a name, and a default value. It can optionally define a "short" and "long" names to be used from the command line (it follows unix command line conventions). It may also specify additional attributes, such as `type` and `help` (see :ref:`below `). See the example: .. literalinclude:: samples/parameters.py For python-actions the python function must define arguments with the same name as a task parameter. .. code-block:: console $ doit py_params -p abc --param2 4 . py_params abc 9 Need a list in your python function? Specify an option with ``type`` set to ``list``. .. code-block:: console $ doit py_params_list -l milk -l eggs -l bread . py_params_list milk eggs bread Choices can be set by specifying an option with ``choices`` set to a sequence of a 2-element tuple. The first element is the choice value. The second element is the choice description, if not required, use an empty string. .. code-block:: console $ doit py_params_choice -c that . py_params_choice that Invalid choices are detected and passed back to the user. .. code-block:: console $ doit py_params_choice -c notavalidchoice ERROR: Error parsing parameter 'choice'. Provided 'notavalidchoice' but available choices are: 'this', 'that'. For cmd-actions use python string substitution notation: .. code-block:: console $ doit cmd_params -f "-c --other value" . cmd_params mycmd -c --other value xxx .. _parameters-attributes: All parameters attributes ^^^^^^^^^^^^^^^^^^^^^^^^^ Here is the list of all attributes ``param`` accepts: ``name`` Name of the parameter, identifier used as name of the the parameter on python code. It should be unique among others. :required: True :type: `str` ``default`` Default value used when it is set through command-line. :required: True ``short`` Short parameter form, used for e.g. ``-p value``. :required: optional :type: `str` ``long`` Long parameter form, used for e.g. ``--parameter value``. :required: optional :type: `str` ``type`` Actually it can be any python callable. It coverts the string value received from command line to whatever value to be used on python code. If the ``type`` is ``bool`` the parameter is treated as an *option flag* where no value should be specified, value is set to ``True``. Example: ``doit mytask --flag``. :required: optional :type: `callable` (e.g. a `function`) :default: `str` ``choices`` List of accepted value choices for option. First tuple element is the value name, second tuple element is a help description for value. :required: optional :type: list of 2-tuple strings ``help`` Help message associated to this parameter, shown when :ref:`help ` is called for this task, e.g. ``doit help mytask``. :required: optional :type: `str` ``inverse`` [only for `bool` parameter] Set inverse flag long parameter name, value will be set to ``False`` (see example below). :required: optional :type: `str` Example, given following code: .. literalinclude:: samples/parameters_inverse.py calls to task `with_flag` show flag on or off: .. code-block:: console $ doit with_flag . with_flag Flag On $ doit with_flag --flagoff . with_flag Flag Off positional arguments -------------------- Tasks might also get positional arguments from the command line as standard unix commands do, with positional arguments *after* optional arguments. .. literalinclude:: samples/pos.py .. code-block:: console $ doit pos_args -p 4 foo bar . pos_args param1 is: 4 positional-0: foo positional-1: bar .. warning:: If a task accepts positional arguments, it is not allowed to pass other tasks after it in the command line. For example if `task1` takes positional arguments you can not call:: $ doit task1 pos1 task2 As the string `task2` would be interpreted as positional argument from `task1` not as another task name. .. _command line variables: command line variables (*doit.get_var*) ----------------------------------------- It is possible to pass variable values to be used in dodo.py from the command line. .. literalinclude:: samples/get_var.py .. code-block:: console $ doit . echo hi {abc: NO} $ doit abc=xyz x=3 . echo hi {abc: xyz} Task creator parameters ----------------------- Command line arguments may also be passed to task creators. It uses the same parameter syntax as is used with task action parameters. .. code-block:: python from doit import task_params @task_params([{"name": "howmany", "default": 3, "type": int, "long": "howmany"}]) def task_subtasks(howmany): for i in range(howmany): yield {"name": i, "actions": [f"echo I can count to {howmany}: {i}"]} Any argument defined for the task generating function will also be available as an argument for any task actions. .. code-block:: python def do_work(foo): print(f'Argument foo={foo}') @task_params([{"name": "foo", "default": "bar", "long": "foo"}]) def task_use_in_action(foo): print(f'When the task action runs it will print {foo}') return { 'actions': [do_work], 'verbosity': 2 } .. warning:: When the ``@task_params`` decorator is used, you must not use the ``params`` field. The content from ``params`` can be easily included in ``@task_params``. The reason for this limitation is that the command line parsing happens before the task's dict is returned. Hence impossible to know its value. doit-0.36.0/doc/task-creation.rst000066400000000000000000000103051423054503100165760ustar00rootroot00000000000000.. meta:: :description: pydoit guide - re-using task definitions & alternative ways to define tasks. :keywords: python, doit, documentation, guide, task, dynamic workflow .. title:: Alternative way of defining tasks - pydoit guide More on Task creation ===================== importing tasks --------------- The *doit* loader will look at **all** objects in the namespace of the *dodo*. It will look for functions staring with ``task_`` and objects with ``create_doit_tasks``. So it is also possible to load task definitions from other modules just by importing them into your *dodo* file. .. literalinclude:: samples/import_tasks.py .. code-block:: console $ doit list echo hello sample .. note:: Importing tasks from different modules is useful if you want to split your task definitions in different modules. The best way to create re-usable tasks that can be used in several projects is to call functions that return task dict's. For example take a look at a reusable *pyflakes* `task generator `_. Check the project `doit-py `_ for more examples. .. _delayed-task-creation: delayed task creation --------------------- `doit` execution model is divided in two phases: - *task-loading* : search for task-creator functions (that starts with string `task_`) and create task metadata - *task-execution* : check which tasks are out-of-date and execute them Normally *task-loading* is completed before the *task-execution* starts. `doit` allows some task metadata to be modified during *task-execution* with `calc_deps` and on `uptodate`, but those are restricted to modifying already created tasks... Sometimes it is not possible to know all tasks that should be created before some tasks are executed. For these cases `doit` supports *delayed task creation*, that means *task-execution* starts before *task-loading* is completed. When *task-creator* function is decorated with `doit.create_after`, its evaluation to create the tasks will be delayed to happen after the execution of the specified task in the `executed` param. .. literalinclude:: samples/delayed.py .. _specify-target-regex: .. note:: To be able to specify targets created by delayed task loaders to `doit run`, it is possible to also specify a regular expression (regex) for every delayed task loader. If specified, this regex should match any target name possibly generated by this delayed task generator. It can be specified via the additional *task-generator* argument `target_regex`. In the above example, the regex `.*\\.out` matches every target name ending with `.out`. It is possible to match every possible target name by specifying `.*`. Alternatively, one can use the command line option `--auto-delayed-regex` to `doit run`; see :ref:`here ` for more information. Parameter: `creates` ++++++++++++++++++++ In case the task created by a `DelayedTask` has a different *basename* than then creator function, or creates several tasks with different *basenames*, you should pass the parameter `creates`. Since `doit` will only execute the body of the task-creator function on demand, the tasks names must be explicitly specified... Example: .. literalinclude:: samples/delayed_creates.py .. warning:: `doit` normally automatically sets `task_dep` between tasks by checking the relation of `file_dep` and `targets`. Due to performance reasons, these `task_dep` relations are NOT computed for delayed-task's `targets`. This problem can avoided by ordering the creation of delayed-tasks with the expected order of execution. .. _create-doit-tasks: custom task definition ------------------------ Apart from collect functions that start with the name `task_`. The *doit* loader will also execute the ``create_doit_tasks`` callable from any object that contains this attribute. .. literalinclude:: samples/custom_task_def.py The `project letsdoit `_ has some real-world implementations. For simple examples to help you create your own check this `blog post `_. doit-0.36.0/doc/tasks.rst000066400000000000000000000431041423054503100151620ustar00rootroot00000000000000.. meta:: :description: pydoit guide - core concepts: tasks, actions, dependencies and targets. :keywords: python, doit, documentation, guide, introduction, task, actions, dependencies, targets, cmd action .. title:: Introduction to Tasks, basic metadata and operations ======== Tasks ======== Intro ------- `doit` is all about automating task dependency management and execution. Tasks can execute external shell commands/scripts or python functions (actually any callable). So a task can be anything you can code :) Tasks are defined in plain `python `_ module with some conventions. .. note:: You should be comfortable with python basics. If you don't know python yet check `Python tutorial `_. A function that starts with the name `task_` defines a *task-creator* recognized by `doit`. These functions must return (or yield) dictionaries representing a *task*. A python module/file that defines *tasks* for `doit` is called **dodo** file (that is something like a `Makefile` for `make`). Take a look at this example (file dodo.py): .. literalinclude:: samples/hello.py When `doit` is executed without any parameters it will look for tasks in a file named `dodo.py` in the current folder and execute its tasks. .. code-block:: console $ doit . hello On the output it displays which tasks were executed. In this case the `dodo` file has only one task, `hello`. Actions -------- Every *task* must define `actions`. It can optionally define other attributes like `targets`, `file_dep`, `verbosity`, `doc` ... `actions` define what the task actually does. `actions` is always a list that can have any number of elements. The actions of a task are always run sequentially. There are 2 basic kinds of `actions`: *cmd-action* and *python-action*. The action "result" is used to determine if task execution was successful or not. python-action ^^^^^^^^^^^^^^ If `action` is a python callable or a tuple `(callable, *args, **kwargs)` - only `callable` is required. The callable must be a function, method or callable object. Classes and built-in functions are not allowed. ``args`` is a sequence and ``kwargs`` is a dictionary that will be used as positional and keywords arguments for the callable. See `Keyword Arguments `_. The result of the task is given by the returned value of the ``action`` function. For **successful** completion it must return one of: * `True` * `None` * a dictionary * a string For **unsuccessful** completion it must return one of: * `False` indicates the task generally failed * if it raises any exception, it will be considered an error * it can also explicitly return an instance of :py:class:`TaskFailed` or :py:class:`TaskError` If the action returns a type other than the types already discussed, the action will be considered a failure, although this behavior might change in future versions. .. literalinclude:: samples/tutorial_02.py The function `task_hello` is a *task-creator*, not the task itself. The body of the task-creator function is always executed when the dodo file is loaded. .. topic:: task-creators vs actions The body of task-creators are executed even if the task is not going to be executed. The body of task-creators should be used to create task metadata only, not execute tasks! From now on when the documentation says that a *task* is executed, read "the task's actions are executed". `action` parameters can be passed as ``kwargs``. .. literalinclude:: samples/task_kwargs.py cmd-action ^^^^^^^^^^^ CmdAction's are executed in a subprocess (using python `subprocess.Popen `_). If `action` is a string, the command will be executed through the shell. (Popen argument shell=True). It is easy to include dynamic (on-the-fly) behavior to your tasks with python code from the `dodo` file. Let's take a look at another example: .. literalinclude:: samples/cmd_actions.py .. note:: The body of the *task-creator* is always executed, so in this example the line `msg = 3 * "hi! "` will always be executed. If `action` is a list of strings and instances of any Path class from `pathlib `_, by default it will be executed **without the shell** (Popen argument shell=False). .. literalinclude:: samples/cmd_actions_list.py For complex commands it is also possible to pass a callable that returns the command string. In this case you must explicit import CmdAction. .. literalinclude:: samples/cmd_from_callable.py You might also explicitly import ``CmdAction`` in case you want to pass extra parameters to ``Popen`` like ``cwd``. All keyword parameter from ``Popen`` can be used on ``CmdAction`` (except ``stdout`` and ``stderr``). .. note:: Different from `subprocess.Popen`, `CmdAction` `shell` argument defaults to `True`. All other `Popen` arguments can also be passed in `CmdAction` except `stdout` and `stderr` The result of the task follows the shell convention. If the process exits with the value `0` it is successful. Any other value means the task failed. .. _custom-actions: custom actions ^^^^^^^^^^^^^^^^^^^ It is possible to create other type of actions, check :ref:`tools.LongRunning` as an example. task name ------------ By default a task name is taken from the name of the python function that generates the task. For example a `def task_hello` would create a task named ``hello``. It is possible to explicitly set a task name with the parameter ``basename``. .. literalinclude:: samples/task_name.py .. code-block:: console $ doit . hello . hello2 When explicit using ``basename`` the task-creator is not limited to create only one task. Using ``yield`` it can generate several tasks at once. It is also possible to ``yield`` a generator that generate tasks. This is useful to write some generic/reusable task-creators. .. literalinclude:: samples/task_reusable.py .. code-block:: console $ doit . t2 . t1 listing tasks ------------- ``doit`` has built-in sub-command that can be used to list/print all tasks. .. code-block:: console $ doit list hello say hello hello2 say hello again doc --- Every task has an associated documentation. By default this documentation is taken from the task-creator function's docstring. It can also be set by the ``doc`` attribute. .. literalinclude:: samples/task_doc.py .. code-block:: console $ doit list . hello say hello sub-tasks --------- Most of the time we want to apply the same task several times in different contexts. The task function can return a python-generator that yields dictionaries. Since each sub-task must be uniquely identified it requires an additional field ``name``. .. literalinclude:: samples/subtasks.py .. code-block:: console $ doit . create_file:file0.txt . create_file:file1.txt . create_file:file2.txt avoiding empty sub-tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^ If you are not sure sub-tasks will be created for a given ``basename`` but you want to make sure that a task exists, you can yield a sub-task with ``name`` equal to ``None``. This can also be used to set the task ``doc`` and ``watch`` attributes. .. literalinclude:: samples/empty_subtasks.py .. code-block:: console $ doit $ doit list do_x docs for X Dependencies & Targets ------------------------- One of the main ideas of `doit` (and other build-tools) is to check if the tasks/targets are **up-to-date**. In case there is no modification in the dependencies and the targets already exist, it skips the task execution to save time, as it would produce the same output from the previous run. Dependency A dependency indicates an input to the task execution. Target A *target* is the result/output file produced by the task execution. i.e. In a compilation task the source file is a *file_dep*, the object file is a *target*. .. literalinclude:: samples/compile.py `doit` automatically keeps track of file dependencies. It saves the signature (MD5) of the dependencies every time the task is completed successfully. So if there are no modifications to the dependencies and you run `doit` again. The execution of the task's actions is skipped. .. code-block:: console $ doit . compile $ doit -- compile Note the ``--`` (2 dashes, one space) on the command output on the second time it is executed. It means, this task was up-to-date and not executed. .. _file-dep: file_dep (file dependency) ----------------------------- Different from most build-tools dependencies are on tasks, not on targets. So `doit` can take advantage of the "execute only if not up-to-date" feature even for tasks that don't define targets. Let's say you work with a dynamic language (python in this example). You don't need to compile anything but you probably want to apply a lint-like tool (i.e. `pyflakes `_) to your source code files. You can define the source code as a dependency to the task. Every dependency in file_dep list should be a string or an instance of any Path class from `pathlib `_. .. literalinclude:: samples/checker.py .. code-block:: console $ doit . checker $ doit -- checker `doit` checks if `file_dep` was modified or not (by comparing the file content's MD5). If there are no changes the action is not executed again as it would produce the same result. Note the ``--`` again to indicate the execution was skipped. Traditional build-tools can only handle files as "dependencies". `doit` has several ways to check for dependencies, those will be introduced later. .. note:: `doit` saves the MD5 of a `file_dep` after the actions are executed. Be careful about editing a `file_dep` while a task is running because `doit` might save the MD5 of a version of the file that is different than it actually used to execute the task. targets ------- Targets can be any file path (a file or folder). If a target does not exist the task will be executed. There is no limitation on the number of targets a task may define. Two different tasks can not have the same target. Target can be specified as a string or as an instance of any Path class from `pathlib `_. Lets take the compilation example again. .. literalinclude:: samples/compile.py * If there are no changes in the dependency the task execution is skipped. * But if the target is removed the task is executed again. * But only if it does not exist. If the target is modified but the dependencies do not change the task is not executed again. .. code-block:: console $ doit . compile $ doit -- compile $ rm main.o $ doit . compile $ echo xxx > main.o $ doit -- compile execution order ----------------- If your tasks interact in a way where the target (output) of one task is a file_dep (input) of another task, `doit` will make sure your tasks are executed in the correct order. .. literalinclude:: samples/taskorder.py .. code-block:: console $ doit . create . modify .. note:: `doit` compares the path (string) of the file of `file_dep` and `targets`. So although `my_file` and `./my_file` are actually the same file, `doit` will think they are different files. .. _task-selection: Task selection ---------------- By default all tasks are executed in the same order as they were defined (the order may change to satisfy dependencies). You can control which tasks will run in 2 ways. Another example .. literalinclude:: samples/selecttasks.py DOIT_CONFIG -> default_tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *dodo* file defines a dictionary ``DOIT_CONFIG`` with ``default_tasks``, a list of strings where each element is a task name. .. code-block:: console $ doit . t1 . t3 Note that only the task *t3* was specified to be executed by default. But its dependencies include a target of another task (t1). So that task was automatically executed also. command line selection ^^^^^^^^^^^^^^^^^^^^^^^ From the command line you can control which tasks are going to be execute by passing its task name. Any number of tasks can be passed as positional arguments. .. code-block:: console $ doit t2 . t2 You can also specify which task to execute by its target: .. code-block:: console $ doit task1 . t1 sub-task selection ^^^^^^^^^^^^^^^^^^^^^ You can select sub-tasks from the command line specifying its full name. .. literalinclude:: samples/subtasks.py .. code-block:: console $ doit create_file:file2.txt . create_file:file2.txt wildcard selection ^^^^^^^^^^^^^^^^^^^^ You can also select tasks to be executed using a `glob `_ like syntax (it must contains a ``*``). .. code-block:: console $ doit "create_file:file*" . create_file:file1.txt . create_file:file2.txt . create_file:file3.txt parameters on actions --------------------- Actions may take optional parameters provided as function keywords arguments for *python-action*. Or values for *printf-style* formatting on *cmd-action*. There are three sources of parameter values: - specified on action's `kwargs` definition - keywords with task metadata such as `dependencies`, `changed`, `targets` and `task` - values computed in other tasks. :ref:`getargs` describes, how a task can calculate and store values and how other tasks can refer to them. keywords with task metadata ^^^^^^^^^^^^^^^^^^^^^^^^^^^ These values are automatically calculated by `doit`: - `dependencies`: list of `file_dep` - `changed`: list of `file_dep` that changed since last successful execution - `targets`: list of `targets` - `task`: only available to *python-action*. Note: the value is a `Task` object instance, not the metadata *dict*. .. _keywords-on-cmd-action-string: keywords on cmd-action string ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For *cmd-action* you can take advantage of implicit keyword substitution on *cmd-action* strings using python formatting. Both format-string-syntax_ and old-string-formatting_ are available and are controlled by ``DOIT_CONFIG`` value of ``action_string_formatting``. It can be: * ``'old'`` uses old-string-formatting_ (``%``) * ``'new'`` uses format-string-syntax_ (``{{}}``) * ``'both'`` both old and new style The keyword value is a string containing all respective file names separated by a space (" "). .. _format-string-syntax: https://docs.python.org/3/library/string.html#format-string-syntax .. _old-string-formatting: http://docs.python.org/3/library/stdtypes.html#old-string-formatting .. literalinclude:: samples/report_deps.py .. warning:: ``action_string_formatting`` default value (as of `doit` version **0.32**) is ``'old'``. This default value might change in future versions, it is advised to always explicitly state a value. Before the string is actually executed, it is always formatted using the formatters specified in `DOIT_CONFIG`. Make sure to escape your formatters control characters, namely `{` and `}` for format-string-syntax_ and `%` for old-string-formatting_. This is done by doubling them, so `%` becomes `%%` and so on. .. note:: *cmd-action* may have the form of a string (as `"echo hello world"`) or list of arguments (as `["echo", "hello", "world"]`). Implicit keywords substitution applies only to the string form and does not affect the list form. This means `{}` and `%` need not be escaped when using this form. keywords on *python-action* ^^^^^^^^^^^^^^^^^^^^^^^^^^^ For *python-action* add a keyword parameter in the function, `doit` will take care of passing the value when the function is called. `dependencies`, `changed`, `targets` are passed as list of strings. .. literalinclude:: samples/hello.py You can also pass the keyword `task` to have a reference to all task metadata. .. literalinclude:: samples/meta.py .. note:: It is possible not only to retrieve task's attributes but also to modify them while the action is running! private/hidden tasks --------------------- If task name starts with an underscore '_', it will not be included in the output. title ------- By default when you run `doit` only the task name is printed out on the output. You can customize the output passing a ``title`` function to the task: .. literalinclude:: samples/title.py .. code-block:: console $ doit . executing... Cmd: echo abc efg .. _verbosity: verbosity ----------- By default the stdout from a task is captured and its stderr is sent to the console. If the task fails or there is an error the stdout and a traceback (if any) is displayed. There are 3 levels of verbosity: 0: capture (do not print) stdout/stderr from task. 1 (default): capture stdout only. 2: do not capture anything (print everything immediately). You can control the verbosity by: * task attribute verbosity .. literalinclude:: samples/verbosity.py .. code-block:: console $ doit . print hello * from command line, see :ref:`verbosity option`. meta ---- You can pass *extra* metadata (dict) to a task through the **meta** attribute. This could be used by custom commands or plugins. .. literalinclude:: samples/metadata.py pathlib -------- `doit` supports `pathlib `_: file_dep, targets and CmdAction specified as a list can take as elements not only strings but also instances of any Path class from pathlib. Lets take the compilation example and modify it to work with any number of header and source files in current directory using pathlib. .. literalinclude:: samples/compile_pathlib.py doit-0.36.0/doc/tools.rst000066400000000000000000000061171423054503100152000ustar00rootroot00000000000000.. meta:: :description: Help snippets for tasks and integrations :keywords: python, doit, documentation, guide, ipython, interactive .. title:: help snippets for tasks and integrations - pydoit guide ==================== Tools & Integrations ==================== `doit.tools` includes some commonly used code. These are not used by the `doit` core, you can see it as a "standard library". The functions/class used with `uptodate` were already introduced in the previous section. create_folder (action) ------------------------- Creates a folder if it does not exist yet. Uses `os.makedirs() _`. .. literalinclude:: samples/folder.py title_with_actions (title) ---------------------------- Return task name task actions from a task. This function can be used as 'title' attribute of a task dictionary to provide more detailed information of the action being executed. .. literalinclude:: samples/titlewithactions.py .. _tools.LongRunning: LongRunning (action) ----------------------------- .. autoclass:: doit.tools.LongRunning This is useful for executing long running process like a web-server. .. literalinclude:: samples/longrunning.py Interactive (action) ---------------------------------- .. autoclass:: doit.tools.Interactive PythonInteractiveAction (action) ---------------------------------- .. autoclass:: doit.tools.PythonInteractiveAction set_trace ----------- `doit` by default redirects stdout and stderr. Because of this when you try to use the python debugger with ``pdb.set_trace``, it does not work properly. To make sure you get a proper PDB shell you should use doit.tools.set_trace instead of ``pdb.set_trace``. .. literalinclude:: samples/settrace.py .. _tools.IPython: IPython integration ---------------------- A handy possibility for interactive experimentation is to define tasks from within *ipython* sessions and use the ``%doit`` `magic function `_ to discover and execute them. First you need to register the new magic function into ipython shell. .. code-block:: pycon >>> %load_ext doit.tools .. Tip:: To permanently add this magic-function to your IPython include it on your `profile `_, create a new script inside your startup-profile (i.e. :file:`~/.ipython/profile_default/startup/doit_magic.ipy`) with the following content:: from doit import load_ipython_extension load_ipython_extension() Then you can define your `task_creator` functions and invoke them with `%doit` magic-function, instead of invoking the cmd-line script with a :file:`dodo.py` file. Examples: .. code-block:: pycon >>> %doit --help ## Show help for options and arguments. >>> def task_foo(): return {'actions': ["echo hi IPython"], 'verbosity': 2} >>> %doit list ## List any tasks discovered. foo >>> %doit ## Run any tasks. . foo hi IPython doit-0.36.0/doc/tutorial-1.rst000066400000000000000000000503231423054503100160370ustar00rootroot00000000000000.. meta:: :description: pydoit tutorial - walkthrough doit basic and intermediate concepts with real-world example :keywords: python, doit, tutorial, getting started, graph, graphviz, task-runner, pipeline .. title:: pydoit tutorial - build a graph of module's imports ====================================== tutorial - python imports graph ====================================== This tutorial demonstrates how to use ``doit`` to create a simple computational pipeline. The goal is to create a graph of python module's imports in a package. `graphviz `_'s **dot** tool will be used to generate the graph. As an example, the `requests `_ package will be used. The result is the image below: .. image:: _static/requests.png In the image an arrow represents an import from one module to another. For example in the left side of image you can see an arrow from `requests.status_codes` to `requests.structures`. This comes from the following line in `status_code.py`: .. code-block:: python3 :caption: requests/status_codes.py from .structures import LookupDict Drawing an "import" dependency graph can be very useful in understanding how the code in a package is organized. It may also help you identify potential problems like circular imports. There are three main steps to generate this graph: 1) read each python module and list its imports 2) generate a `dot` (text format) file representing the graph 3) generate an image (PNG) from the `dot` file setup ===== required python packages ------------------------ .. code-block:: console $ pip install doit pygraphviz import_deps Note that on some linux systems it is necessary to install the system package `graphviz-dev` first. sample project -------------- First create a directory that will contain the projects to be analyzed. .. code-block:: console $ mkdir projects Then clone the ``requests`` project .. code-block:: console $ cd projects $ git clone git@github.com:requests/requests.git $ cd .. finding a module's import ========================= Using `import_deps `_ list all (intra-packages) imports from a module: For example: .. code-block:: console $ python -m import_deps projects/requests/requests/models.py requests._internal_utils requests.auth requests.compat requests.cookies requests.exceptions requests.hooks requests.status_codes requests.structures requests.utils The output contains one imported module per line. doit task --------- On the next step we are going to wrap the above script in a ``doit`` *task*. In ``doit`` tasks are defined in a plain python module, by default called ``dodo.py``. For example, a trivial task to execute the script above and save its output into a file would be: .. code-block:: python3 :caption: dodo.py def task_imports(): return { 'actions': ['python -m import_deps ' 'projects/requests/requests/models.py > requests.models.deps'], } In this module you write functions that are **task-creators**, the role of which is not to execute tasks but to return tasks' metadata. **task-creators** are any function whose name starts with ``task_``. A task name is taken from the function name, so in this case the task is called ``imports``. The most important *Task* metadata is ``actions``, which defines what will be done when a task is executed. Note that ``actions`` is a list where its element are strings to be interpreted as shell commands. task execution -------------- ``doit`` comes with a command line tool to act upon the set of tasks defined in a specific file. The default file is ``dodo.py`` in the current directory. With no argument it executes all tasks found in it. .. code-block:: console $ doit . imports The output reports that the ``imports`` task was executed. You can check that a file ``requests.models.deps`` was created with a list of modules imported by ``requests.models``. incremental computation ----------------------- One of the main purposes of ``doit`` is to make use of **up-to-date** checks to decide if tasks *need* to be executed or not. In our case, as long as the input file is not modified we are certain that the same output would be generated... When dealing with files, task's metadata ``file_dep`` and ``targets`` can be used: .. task_imports, line 11 is clean .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 5-10,12 Note how ``actions`` can make use of variable substitution for ``%(dependencies)s`` and ``%(targets)s``. Now let's execute it again: .. code-block:: console $ doit . imports And then, a second time: .. code-block:: console $ doit -- imports Note that the second time there is a ``--`` instead of ``.`` preceding the task name. This means that the task was not executed, ``doit`` understood that the task output would be the same as previously generated, so it does not execute the task again. .. warning:: When ``doit`` *loads* a ``dodo.py`` file it executes all *task-creator* functions in order to generate all tasks metadata. A task's ``action`` is only executed if the task is selected to run and not **up-to-date**. Expensive computation should always be done on task's ``action`` and never on the body of a **task-creator** function. rules for up-to-date checks on files ------------------------------------ file_dep ^^^^^^^^ ``doit`` uses the *md5* of ``file_dep`` to determine if a dependency has changed. .. code-block:: console $ touch projects/requests/requests/models.py $ doit -- imports $ echo "# comment" >> projects/requests/requests/models.py $ doit . imports Note that simply changing a file timestamp does not trigger a new execution. targets ^^^^^^^ For ``targets``, the only verification that is made is whether the file exists or not. So if a target is removed it will be re-created even if the dependencies remain unmodified. .. code-block:: console $ rm requests.models.deps $ doit . imports graphviz dot ============ On the next step we will create a `graphviz `_'s ``dot`` file. ``dot`` is a language to describe graphs. The code below defines a python function to read a file containing import dependencies (as generated by our previously defined ``imports`` task). .. module_to_dot() .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 1-3,14-15,18-27 Task with python action ----------------------- Next we define the ``dot`` task, which is similar to previous tasks... except for the fact that instead of passing a string with a shell command we directly pass the previously created python function ``module_to_dot``. .. task_dot() .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 28-33,35 Also note that the function takes the special parameters ``dependencies`` and ``targets``, whose values will be injected by ``doit`` in the function call. .. code-block:: console $ doit -- imports . dot To indicate a failure, a python-action should return the value ``False`` or raise an exception. graph image ----------- Finally lets add another task to generate an image from the `dot` file using the graphviz command line tool. .. task_draw() .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 38-43,45 .. code-block:: console $ doit -- imports -- dot . draw Opening the file ``requests.models.png`` you should get the image below: .. image:: _static/requests.models.png doit command line ================= ``doit`` has a rich (and extensible) command line tool to manipulate your tasks. So far we have only executed ``doit`` without any parameters... ``doit`` command line takes the form of ``doit ``, where ``options`` and ``arguments`` are specific to the ``sub-command``. If no sub-command is specified the default command ``run`` is used. ``run`` executes tasks... doit help --------- ``doit help`` will list all available sub-commands. You can get help for a specific sub-command with ``doit help ``, i.e. ``doit help run``. You can also get help for the task metadata fields with ``doit help task``. doit list --------- The command ``list`` displays the list of known tasks: .. code-block:: console $ doit list dot generate a graphviz's dot graph from module imports draw generate image from a dot file imports find imports from a python module Note how the docstring from *task-creators* functions were used as tasks' description. info ---- The ``info`` command can be used to get more information about a specific task's metadata and state (whether it is up-to-date or not). .. code-block:: console $ doit info imports imports find imports from a python module status : up-to-date file_dep : - projects/requests/requests/models.py targets : - requests.models.deps run --- ``run`` is the default command, and usually not explicitly typed. So ``$ doit`` and ``$ doit run`` do exactly the same thing. Without any parameters ``run`` will execute all of your tasks. You can also select which tasks to be executed by passing a sequence of tasks' names. For example if you want to execute only the ``imports`` task you would type: .. code-block:: console $ doit imports -- imports Note that even if you explicitly pass the name of the task to be executed, ``doit`` will actually execute the task only if it is not **up-to-date**. You can also pass more than one task: .. code-block:: console $ doit imports dot -- imports -- dot Another important point to take notice of is that even if you specify only one task, ``doit`` will run all of the dependencies of the specified task. .. code-block:: console $ doit dot -- imports -- dot Note how the ``imports`` task was run because task ``dot`` has ``file_dep`` that is a target of ``imports`` task. clean ----- A common use-case is to be able to "revert" the operations done by a task. ``doit`` provides the ``clean`` command for that. By default it does nothing... You need to add the parameter ``clean`` to the task's metadata. For the most common case where you just want to remove the created targets, just pass the value ``True``. You can also write custom ``actions`` (shell or python) to specify what should be done as a value to ``clean`` field. Add ``clean`` to all defined tasks, like: .. task_draw() .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 38-45 :emphasize-lines: 7 Executing ``clean``: .. code-block:: console $ doit clean draw - removing file 'requests.models.png' dot - removing file 'requests.models.dot' imports - removing file 'requests.models.deps' Since targets were removed this will force the tasks to be executed on next ``run``. .. code-block:: console $ doit . imports . dot . draw forget ------ ``doit`` will look for changes in the dependencies, but not for changes in the code that defines the tasks... While developing a task, it is common to want to force its execution after making changes to it. For example, let's change the colors of the nodes in the graph: .. module_to_dot() .. literalinclude:: tutorial/tuto_1_1.py :language: python3 :lines: 14-21 :emphasize-lines: 3-4 To force its execution we need ``doit`` to ``forget`` its state thus so: .. code-block:: console $ doit forget dot forgetting dot .. code-block:: console $ doit -- imports . dot . draw .. image:: _static/requests.models-blue.png .. note:: Another option to force the execution of a task after code changes is to use `run``'s command option ``-a/--always-execute``. That will ignore the **up-to-date** check and always execute tasks. Code :download:`dodo.py `. Pipelines ========= So far we have built a traditional "file" based pipeline where one task's target is used as a dependency for another task. While ``doit`` provides first-class support for file based pipelines, they are not required. get module imports - python --------------------------- Let's rewrite the ``imports`` task to use a python action instead of a shell command: .. task_imports() .. literalinclude:: tutorial/tuto_1_2.py :language: python3 :lines: 4-19 :emphasize-lines: 8,15 The function ``get_imports`` is used as the task's action. It returns a dictionary, which will be saved by ``doit`` in its internal database. The returned dictionary must contain only values that can be encoded as JSON. ``get_imports`` takes the path's module as a parameter (``module_path``). The value that will be used for this parameter upon task execution is specified in the ``actions`` of the task definition. Generally speaking, each element of the ``actions`` array is a tuple *(callable, args, kwargs)*. .. note:: Note in this example for simplicity we are using ``doit`` internal database, but it is also possible to use any other external database or data source. getargs ------- The task's parameter ``getargs`` can be used to extract values from another task's result and pass it as a parameter to the current task's action. It's a dictionary of the form .. code-block:: python {target_key: (task_name, source_key)} and what it does is to execute the task ``task_name``, get the value of ``source_key`` from its resulting dict, and passing that as the argument named ``target_key`` of the current task's action. .. task_dot() .. literalinclude:: tutorial/tuto_1_2.py :language: python3 :lines: 22-38 :emphasize-lines: 1,14,15 Note how ``module_to_dot`` takes 3 parameters: - ``source``: value is passed directly when the task's actions is defined - ``sinks``: value is taken from ``imports`` task's result - ``targets``: values is taken from Task metadata Everything should work as before, but without the creation of intermediate files. ``doit`` can determine if the task ``imports`` is **up-to-date** even without a target file (it will just look at the ``file_dep``). ``doit`` can also determine if ``dot`` is **up-to-date** by comparing the value returned by ``imports`` (its dependency through the ``getargs`` parameter), with the value stored in its database. Code :download:`dodo.py `. package imports =============== So far we have been creating a graph of a single module. Let's process all modules in the package. ``doit`` has the concept of a **task-group**. A task group performs the same operation over a set of instances. To create a task group the task-creator function should ``yield`` one or more task dictionaries with task metadata. Note that each task is still independent. Since each task needs to be independently identified an extra parameter ``name`` must be provided. .. task_imports() .. literalinclude:: tutorial/tuto_1_3.py :language: python3 :lines: 8-22 :emphasize-lines: 4,11,12 Sub-tasks (items of task group) by default are not reported by the ``list`` command. They can be displayed, though, using the ``--all`` flag. .. code-block:: console $ doit list dot generate a graphviz's dot graph from module imports draw generate image from a dot file imports find imports from a python module .. code-block:: console $ doit list --all imports imports find imports from a python module imports:requests.__init__ imports:requests.__version__ imports:requests._internal_utils imports:requests.adapters imports:requests.api imports:requests.auth imports:requests.certs imports:requests.compat imports:requests.cookies imports:requests.exceptions imports:requests.help imports:requests.hooks imports:requests.models imports:requests.packages imports:requests.sessions imports:requests.status_codes imports:requests.structures imports:requests.utils Note the task's name is composed of the task's group name (aka ``basename``) followed by a colon `:` and the ``name`` specified as a parameter when ``yield``. From the command line, a single task can be executed like this:: $ doit imports:requests.models . imports:requests.models getargs from group-task ----------------------- ``getargs`` can also be used to get values from a *group-task*. The difference is that its value will be a dictionary where the key is the sub-task name: .. task_dot() .. literalinclude:: tutorial/tuto_1_3.py :language: python3 :lines: 25-41 :emphasize-lines: 5,13-15 Finally, adjust task ``draw``. .. task_draw() .. literalinclude:: tutorial/tuto_1_3.py :language: python3 :lines: 44-51 :emphasize-lines: 4-5 Running ``doit`` you should get the file ``requests.png`` with the image below: .. image:: _static/requests.png Code :download:`dodo.py `. printing imports ================ Getting rid of intermediate computation files (like ``requests.models.deps``) was nice... but sometimes it is useful to be able to quickly list the direct imports from a module. Let's create another task that just prints its output in the terminal. .. task_print() .. literalinclude:: tutorial/tuto_1_4.py :language: python3 :lines: 12-14, 32-44 :emphasize-lines: 14-15 Here again we used a **task-group** to create one task per python module and ``getargs`` to extract the list of modules' imports from ``imports``'s result. Also note the usage of two metadata fields not seen before: ``uptodate`` and ``verbosity``. custom `uptodate` ----------------- So far we have seen how ``doit`` can determine if a task is **up-to-date** by taking into consideration changes to ``file_dep``, if ``targets`` exist and results from ``getargs`` have changed. While those cover a wide range of use cases, ``doit`` also provides a way to specify completely custom checks for **up-to-date**, using the ``uptodate`` field. In this case the ``print`` task actually does not perform any computation, it is being used to display some info to the user. So this task should be **always** executed. ``uptodate`` will be explained in detail in part 2 of this tutorial. For now it suffices to add the value ``False`` to indicate this task will never be considered **up-do-date**. .. code-block:: python 'uptodate': [False], verbosity --------- ``doit`` output (for command ``run``) consists of: - one line with task name (preceded by `.` or `--`) - task's output The actual task's output displayed can be controlled by ``verbosity``. There are 3 levels of verbosity. - 0: both stdout and stderr from the task are **NOT** displayed - 1: only stderr is displayed - 2: both stdout and stderr are displayed The default verbosity is `1`. If the ``print`` task would be executed with the default verbosity we would actually not see any output, so we must force its ``verbosity`` value to ``2``. .. code-block:: python3 'verbosity': 2, .. code-block:: console $ doit print:requests.models -- imports:requests.models . print:requests.models requests._internal_utils requests.auth requests.compat requests.cookies requests.exceptions requests.hooks requests.status_codes requests.structures requests.utils Note ``verbosity`` can be overwritten from command-line with option ``-v/--verbosity``. DOIT_CONFIG ----------- There is one last problem to be solved. Since ``print`` is used only to display some information, it should not be executed by default. It should be executed only when explicitly asked. i.e. when you just run ``doit`` without any parameters it should create the graph image but not print out the information from ``print`` task. Before I said that by default ``doit run`` would execute all tasks. That is not exactly true... it will execute all *default tasks*. The default tasks can be controlled by adding a configuration dictionary with the name ``DOIT_CONFIG`` in the `dodo.py`. .. DOIT_CONFIG .. literalinclude:: tutorial/tuto_1_4.py :language: python3 :lines: 7-9 Apart from ``default_tasks``, ``DOIT_CONFIG`` can change the default of any command line option. For example you can globally change every task's ``verbosity`` level: .. code-block:: python3 DOIT_CONFIG = { 'default_tasks': ['imports', 'dot', 'draw'], 'verbosity': 2, } Code :download:`dodo.py `. And that's all for part 1 of tutorial :) doit-0.36.0/doc/tutorial/000077500000000000000000000000001423054503100151445ustar00rootroot00000000000000doit-0.36.0/doc/tutorial/tuto_1_1.py000066400000000000000000000024421423054503100171530ustar00rootroot00000000000000import pathlib import pygraphviz def task_imports(): """find imports from a python module""" return { 'file_dep': ['projects/requests/requests/models.py'], 'targets': ['requests.models.deps'], 'actions': ['python -m import_deps %(dependencies)s > %(targets)s'], 'clean': True, } def module_to_dot(dependencies, targets): graph = pygraphviz.AGraph(strict=False, directed=True) graph.node_attr['color'] = 'lightblue2' graph.node_attr['style'] = 'filled' for dep in dependencies: filepath = pathlib.Path(dep) source = filepath.stem with filepath.open() as fh: for line in fh: sink = line.strip() if sink: graph.add_edge(source, sink) graph.write(targets[0]) def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'file_dep': ['requests.models.deps'], 'targets': ['requests.models.dot'], 'actions': [module_to_dot], 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.models.dot'], 'targets': ['requests.models.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } doit-0.36.0/doc/tutorial/tuto_1_2.py000066400000000000000000000025571423054503100171630ustar00rootroot00000000000000import pathlib import pygraphviz from import_deps import PyModule, ModuleSet def get_imports(module_path): module = PyModule(module_path) base_path = module.pkg_path().resolve() mset = ModuleSet(base_path.glob('**/*.py')) imports = mset.get_imports(module, return_fqn=True) return {'modules': list(sorted(imports))} def task_imports(): """find imports from a python module""" module_path = 'projects/requests/requests/models.py' return { 'file_dep': [module_path], 'actions': [(get_imports, [module_path])], } def module_to_dot(source, sinks, targets): graph = pygraphviz.AGraph(strict=False, directed=True) graph.node_attr['color'] = 'lightblue2' graph.node_attr['style'] = 'filled' for sink in sinks: graph.add_edge(source, sink) graph.write(targets[0]) def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'targets': ['requests.models.dot'], 'actions': [(module_to_dot, (), {'source': 'requests.models'})], 'getargs': {'sinks': ('imports', 'modules')}, 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.models.dot'], 'targets': ['requests.models.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } doit-0.36.0/doc/tutorial/tuto_1_3.py000066400000000000000000000027131423054503100171560ustar00rootroot00000000000000import pathlib import pygraphviz from import_deps import PyModule, ModuleSet def get_imports(pkg_modules, module_path): module = pkg_modules.by_path[module_path] imports = pkg_modules.get_imports(module, return_fqn=True) return {'modules': list(sorted(imports))} def task_imports(): """find imports from a python module""" base_path = pathlib.Path('projects/requests/requests') pkg_modules = ModuleSet(base_path.glob('**/*.py')) for name, module in pkg_modules.by_name.items(): yield { 'name': name, 'file_dep': [module.path], 'actions': [(get_imports, (pkg_modules, module.path))], } def module_to_dot(imports, targets): graph = pygraphviz.AGraph(strict=False, directed=True) graph.node_attr['color'] = 'lightblue2' graph.node_attr['style'] = 'filled' for source, sinks in imports.items(): for sink in sinks: graph.add_edge(source, sink) graph.write(targets[0]) def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'targets': ['requests.dot'], 'actions': [module_to_dot], 'getargs': {'imports': ('imports', 'modules')}, 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.dot'], 'targets': ['requests.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } doit-0.36.0/doc/tutorial/tuto_1_4.py000066400000000000000000000036531423054503100171630ustar00rootroot00000000000000import pathlib import pygraphviz from import_deps import PyModule, ModuleSet DOIT_CONFIG = { 'default_tasks': ['imports', 'dot', 'draw'], } base_path = pathlib.Path('projects/requests/requests') PKG_MODULES = ModuleSet(base_path.glob('**/*.py')) def get_imports(pkg_modules, module_path): module = pkg_modules.by_path[module_path] imports = pkg_modules.get_imports(module, return_fqn=True) return {'modules': list(sorted(imports))} def task_imports(): """find imports from a python module""" for name, module in PKG_MODULES.by_name.items(): yield { 'name': name, 'file_dep': [module.path], 'actions': [(get_imports, (PKG_MODULES, module.path))], } def print_imports(modules): print('\n'.join(modules)) def task_print(): """print on stdout list of direct module imports""" for name, module in PKG_MODULES.by_name.items(): yield { 'name': name, 'actions': [print_imports], 'getargs': {'modules': ('imports:{}'.format(name), 'modules')}, 'uptodate': [False], 'verbosity': 2, } def module_to_dot(imports, targets): graph = pygraphviz.AGraph(strict=False, directed=True) graph.node_attr['color'] = 'lightblue2' graph.node_attr['style'] = 'filled' for source, sinks in imports.items(): for sink in sinks: graph.add_edge(source, sink) graph.write(targets[0]) def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'targets': ['requests.dot'], 'actions': [module_to_dot], 'getargs': {'imports': ('imports', 'modules')}, 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.dot'], 'targets': ['requests.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } doit-0.36.0/doc/uptodate.rst000066400000000000000000000110431423054503100156570ustar00rootroot00000000000000.. meta:: :description: Customize the up-to-date check for incremental task execution :keywords: python, doit, documentation, guide, incremental-build, result-dependency, run_one, config_changed, timeout .. title:: Customize the up-to-date check for incremental execution ================= custom up-to-date ================= The basics of `uptodate` was already :ref:`introduced `. Here we look in more detail into some implementations shipped with `doit`. .. _result_dep: result-dependency ---------------------- In some cases you can not determine if a task is "up-to-date" only based on input files, the input could come from a database or an external process. *doit* defines a "result-dependency" to deal with these cases without need to create an intermediate file with the results of the process. i.e. Suppose you want to send an email every time you run *doit* on a mercurial repository that contains a new revision number. .. literalinclude:: samples/taskresult.py Note the `result_dep` with the name of the task ('version'). `doit` will keep track of the output of the task *version* and will execute *send_email* only when the mercurial repository has a new version since last time *doit* was executed. The "result" from the dependent task compared between different runs is given by its last action. The content for python-action is the value of the returned string or dict. For cmd-actions it is the output send to stdout plus stderr. `result_dep` also supports group-tasks. In this case it will check that the result of all subtasks did not change. And also the existing sub-tasks are the same. .. _run_once: run_once() --------------- Sometimes there is no dependency for a task but you do not want to execute it all the time. With "run_once" the task will not be executed again after the first successful run. This is mostly used together with targets. Suppose you need to download something from internet. There is no dependency, but you do not want to download it many times. .. literalinclude:: samples/download.py Note that even with *run_once* the file will be downloaded again in case the target is removed. .. code-block:: console $ doit . get_pylogo $ doit -- get_pylogo $ rm python-logo.gif $ doit . get_pylogo .. _timeout: timeout() ----------- ``timeout`` is used to expire a task after a certain time interval. i.e. You want to re-execute a task only if the time elapsed since the last time it was executed is bigger than 5 minutes. .. literalinclude:: samples/timeout.py ``timeout`` is function that takes an ``int`` (seconds) or ``timedelta`` as a parameter. It returns a callable suitable to be used as an ``uptodate`` callable. .. _config_changed: config_changed() ----------------- ``config_changed`` is used to check if any "configuration" value for the task has changed. Config values can be a string or dict. For dict's the values are converted to string (using `json.dumps()` with `sort_key=True`) and only a digest/checksum of the dictionaries keys and values are saved. If converting the values of the dict requires a special encoder this can be passed with the argument ``encoder=...``. This will be passed on to `json.dumps()`. .. literalinclude:: samples/config_params.py .. _check_timestamp_unchanged: check_timestamp_unchanged() ----------------------------- ``check_timestamp_unchanged`` is used to check if specified timestamp of a given file/dir is unchanged since last run. The timestamp field to check defaults to ``mtime``, but can be selected by passing ``time`` parameter which can be one of: ``atime``, ``ctime``, ``mtime`` (or their aliases ``access``, ``status``, ``modify``). Note that ``ctime`` or ``status`` is platform dependent. On Unix it is the time of most recent metadata change, on Windows it is the time of creation. See `Python library documentation for os.stat`__ and Linux man page for stat(2) for details. __ http://docs.python.org/library/os.html#os.stat It also accepts an ``cmp_op`` parameter which defaults to ``operator.eq`` (==). To use it pass a callable which takes two parameters (prev_time, current_time) and returns True if task should be considered up-to-date, False otherwise. Here ``prev_time`` is the time from the last successful run and ``current_time`` is the time obtained in current run. If the specified file does not exist, an exception will be raised. If a file is a target of another task you should probably add ``task_dep`` on that task to ensure the file is created before it is checked. .. literalinclude:: samples/check_timestamp_unchanged.py doit-0.36.0/doc/usecases.rst000066400000000000000000000105421423054503100156500ustar00rootroot00000000000000.. meta:: :description: pydoit is a generic tool based on build-tool concepts. Flexible and scalable. :keywords: python, doit, CLI, linux, windows, task-runner, build-tool, pipeline, workflow, incremental build, data pipeline .. title:: pydoit use cases - from CLI task-runner to complex pipelines ========= Use Cases ========= Here are some use cases, where `doit` can help you with automation of your tasks. Simplify cumbersome command line calls ====================================== Do you have to repeatedly call complex command like this? .. code-block:: console $ aws s3 sync _built/html s3://buck/et --exclude "*" --include "*.html" Wrap it into `dodo.py` file: .. code-block:: python def task_publish(): """Publish to AWS S3""" return { "actions": [ 'aws s3 sync _built/html s3://buck/et --exclude "*" --include "*.html"' ] } and next time just: .. code-block:: console $ doit publish It is easy to include multiple actions into one task or use multiple tasks. Automate typical project related actions ======================================== Do you have to lint your code, run test suite, evaluate coverage, generate documentation incl. spelling? Create the `dodo.py`, which defines tasks you have to do and next time: .. code-block:: console $ doit list coverage show coverage for all modules including tests coverage_module show coverage for individual modules coverage_src show coverage for all modules (exclude tests) package create/upload package to pypi pyflakes pypi create/upload package to pypi spell spell checker for doc files sphinx build sphinx docs tutorial_check check tutorial sample are at least runnable without error ut run unit-tests website dodo file create website html files website_update update website on SITE_PATH and then decide which task to run: .. code-block:: console $ doit spell Share unified way of doing things ================================= Do you expect your colleagues perform the same steps before committing changes to repository? What to do with the complains the steps are too complex? Provide them with the `dodo.py` file doing the things. What goes easy, is more likely to be used. `dodo.py` will become easy to use prescription of best practices. Optimize processing time by skipping tasks already done ======================================================= You dump your database and convert the data to CSV. It takes minutes, but often the input is the same as before. Why to do things already done and wait? Wrap the conversion into `doit` task and `doit` will automatically detect, the input and output are already in sync and complete in fraction of a second, when possible. Manage complex set of depending tasks ===================================== The system you code shall do many small actions, which are interdependent. Split it into small tasks, define (file) dependencies and let `doit` do the planning of what shall be processed first and what next. Your solution will be clean and modular. Speed up by parallel task execution =================================== You already have bunch of tasks defined, results are correct, it only takes so much time. But wait, you have multi-core machine! Just ask for parallel processing: .. code-block:: console $ doit -n 4 and `doit` will take care of planning and make all your CPU cores hot. No need to rewrite your processing from scratch, properly declared tasks is all what you have to provide. Extend your project by doit features ==================================== Your own python project would need features of `doit`, but you cannot ask your users to call `doit` on command line? Simply integrate `doit` functionality into your own command line tool and nobody will notice where it comes from. Create cross-platform tool for processing your stuff ===================================================== Do you have team members working on MS Windows and others on Linux? Scripts are great, but all those small shell differences prevent single reusable solution. With `dodo.py` and python you are more likely to write the processing in cross-platform way. Use `pathlib.Path` and `shutils` magic to create directories, move files around, copy them, etc. doit-0.36.0/doc_requirements.txt000066400000000000000000000002351423054503100166450ustar00rootroot00000000000000# modules required to generate documentation # $ pip install --requirement doc_requirements.txt sphinx sphinx_press_theme sphinx-sitemap sphinx_reredirects doit-0.36.0/dodo.py000077500000000000000000000076771423054503100140570ustar00rootroot00000000000000"""dodo file. test + management stuff""" import glob import os import pytest from doitpy.pyflakes import Pyflakes from doitpy.coverage import Config, Coverage, PythonPackage from doitpy import docs from doitpy.package import Package DOIT_CONFIG = { 'minversion': '0.24.0', 'default_tasks': ['pyflakes', 'ut'], # 'backend': 'sqlite3', 'forget_disable_default': True, } CODE_FILES = glob.glob("doit/*.py") TEST_FILES = glob.glob("tests/test_*.py") TESTING_FILES = glob.glob("tests/*.py") PY_FILES = CODE_FILES + TESTING_FILES def task_pyflakes(): flaker = Pyflakes() yield flaker('dodo.py') yield flaker.tasks('doit/*.py') yield flaker.tasks('tests/*.py') def run_test(test): return not bool(pytest.main([test])) #return not bool(pytest.main("-v " + test)) def task_ut(): """run unit-tests""" for test in TEST_FILES: yield {'name': test, 'actions': [(run_test, (test,))], 'file_dep': PY_FILES, 'verbosity': 0} def task_coverage(): """show coverage for all modules including tests""" config = Config(branch=False, parallel=True, concurrency='multiprocessing', omit=['tests/myecho.py', 'tests/sample_process.py']) cov = Coverage([PythonPackage('doit', 'tests')], config=config) yield cov.all() yield cov.src() yield cov.by_module() ############################ website DOC_ROOT = 'doc/' DOC_BUILD_PATH = DOC_ROOT + '_build/html/' def task_rm_index(): """remove/clean copied index.html if source changed""" # work around https://github.com/sphinx-doc/sphinx/issues/1649 return { 'actions': ['cd doc && make clean'], 'file_dep': ['doc/index.html'], } def task_docs(): doc_files = glob.glob('doc/*.rst') doc_files += ['README.rst', 'CONTRIBUTING.md', 'doc/open_collective.md'] yield docs.spell(doc_files, 'doc/dictionary.txt') sphinx_opts = "-A include_analytics=1 -A include_donate=1" yield docs.sphinx(DOC_ROOT, DOC_BUILD_PATH, sphinx_opts=sphinx_opts, task_dep=['spell', 'rm_index']) def task_samples_check(): """check samples are at least runnuable without error""" black_list = [ 'longrunning.py', # long running doesn't terminate on its own 'settrace.py', 'download.py', # uses network 'taskresult.py', # uses mercurial 'tar.py', # uses mercurial 'calc_dep.py', # uses files not created by the script 'report_deps.py', # uses files not created by the script 'doit_config.py', # no tasks defined ] exclude = set('doc/samples/{}'.format(m) for m in black_list) arguments = {'doc/samples/pos.py': 'pos_args -p 4 foo bar'} for sample in glob.glob("doc/samples/*.py"): if sample in exclude: continue args = arguments.get(sample, '') yield { 'name': sample, 'actions': ['doit -f {} {}'.format(sample, args)], } def task_website(): """dodo file create website html files""" return {'actions': None, 'task_dep': ['sphinx', 'samples_check'], } def task_website_update(): """update website on SITE_PATH website is hosted on github-pages this task just copy the generated content to SITE_PATH, need to commit/push to deploy site. """ SITE_PATH = '../doit-website' SITE_URL = 'pydoit.org' return { 'actions': [ "rsync -avP %s %s" % (DOC_BUILD_PATH, SITE_PATH), "echo %s > %s" % (SITE_URL, os.path.join(SITE_PATH, 'CNAME')), "touch %s" % os.path.join(SITE_PATH, '.nojekyll'), ], 'task_dep': ['website'], } def task_package(): """create/upload package to pypi""" pkg = Package() yield pkg.revision_git() yield pkg.manifest_git() yield pkg.sdist() # yield pkg.sdist_upload() def task_codestyle(): return { 'actions': ['pycodestyle doit'], } doit-0.36.0/doit/000077500000000000000000000000001423054503100134735ustar00rootroot00000000000000doit-0.36.0/doit/__init__.py000066400000000000000000000031721423054503100156070ustar00rootroot00000000000000"""doit - Automation Tool The MIT License Copyright (c) 2008-present Eduardo Naufel Schettino 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 doit.version import VERSION __version__ = VERSION from doit import loader from doit.loader import create_after, task_params from doit.doit_cmd import get_var from doit.api import run from doit.tools import load_ipython_extension from doit.globals import Globals __all__ = ['get_var', 'run', 'create_after', 'task_params', 'Globals'] def get_initial_workdir(): """working-directory from where the doit command was invoked on shell""" return loader.initial_workdir assert load_ipython_extension # silence pyflakes doit-0.36.0/doit/__main__.py000066400000000000000000000003661423054503100155720ustar00rootroot00000000000000# lazy way to ignore coverage in this file if True: # pragma: no cover def main(): import sys from doit.doit_cmd import DoitMain sys.exit(DoitMain().run(sys.argv[1:])) if __name__ == '__main__': main() doit-0.36.0/doit/action.py000066400000000000000000000457111423054503100153320ustar00rootroot00000000000000"""Implements actions used by doit tasks """ import os import sys import subprocess import io from io import StringIO import inspect from pathlib import PurePath from threading import Thread import pdb from .exceptions import InvalidTask, TaskFailed, TaskError def normalize_callable(ref): """return a list with (callable, *args, **kwargs) ref can be a simple callable or a tuple """ if isinstance(ref, tuple): return list(ref) return [ref, (), {}] # Actions class BaseAction(object): """Base class for all actions""" # must implement: # def execute(self, out=None, err=None) @staticmethod def _prepare_kwargs(task, func, args, kwargs): """ Prepare keyword arguments (targets, dependencies, changed, cmd line options) Inspect python callable and add missing arguments: - that the callable expects - have not been passed (as a regular arg or as keyword arg) - are available internally through the task object """ # Return just what was passed in task generator # dictionary if the task isn't available if not task: return kwargs func_sig = inspect.signature(func) sig_params = func_sig.parameters.values() func_has_kwargs = any(p.kind == p.VAR_KEYWORD for p in sig_params) # use task meta information as extra_args meta_args = { 'task': lambda: task, 'targets': lambda: list(task.targets), 'dependencies': lambda: list(task.file_dep), 'changed': lambda: list(task.dep_changed), } # start with dict passed together on action definition kwargs = kwargs.copy() bound_args = func_sig.bind_partial(*args) # add meta_args for key in meta_args.keys(): # check key is a positional parameter if key in func_sig.parameters: sig_param = func_sig.parameters[key] # it is forbidden to use default values for this arguments # because the user might be unaware of this magic. if (sig_param.default != sig_param.empty): msg = (f"Task {task.name}, action {func.__name__}():" f"The argument '{key}' is not allowed to have " "a default value (reserved by doit)") raise InvalidTask(msg) # if value not taken from position parameter if key not in bound_args.arguments: kwargs[key] = meta_args[key]() # add tasks parameter options opt_args = dict(task.options) if task.pos_arg is not None: opt_args[task.pos_arg] = task.pos_arg_val for key in opt_args.keys(): # check key is a positional parameter if key in func_sig.parameters: # if value not taken from position parameter if key not in bound_args.arguments: kwargs[key] = opt_args[key] # if function has **kwargs include extra_arg on it elif func_has_kwargs and key not in kwargs: kwargs[key] = opt_args[key] return kwargs class CmdAction(BaseAction): """ Command line action. Spawns a new process. @ivar action(str,list,callable): subprocess command string or string list, see subprocess.Popen first argument. It may also be a callable that generates the command string. Strings may contain python mappings with the keys: dependencies, changed and targets. ie. "zip %(targets)s %(changed)s" @ivar task(Task): reference to task that contains this action @ivar save_out: (str) name used to save output in `values` @ivar shell: use shell to execute command see subprocess.Popen `shell` attribute @ivar encoding (str): encoding of the process output @ivar decode_error (str): value for decode() `errors` param while decoding process output @ivar pkwargs: Popen arguments except 'stdout' and 'stderr' """ STRING_FORMAT = 'old' def __init__(self, action, task=None, save_out=None, shell=True, encoding='utf-8', decode_error='replace', buffering=0, **pkwargs): # pylint: disable=W0231 ''' :ivar buffering: (int) stdout/stderr buffering. Not to be confused with subprocess buffering - 0 -> line buffering - positive int -> number of bytes ''' for forbidden in ('stdout', 'stderr'): if forbidden in pkwargs: msg = "CmdAction can't take param named '{0}'." raise InvalidTask(msg.format(forbidden)) self._action = action self.task = task self.out = None self.err = None self.result = None self.values = {} self.save_out = save_out self.shell = shell self.encoding = encoding self.decode_error = decode_error self.pkwargs = pkwargs self.buffering = buffering @property def action(self): if isinstance(self._action, (str, list)): return self._action else: # action can be a callable that returns a string command ref, args, kw = normalize_callable(self._action) kwargs = self._prepare_kwargs(self.task, ref, args, kw) return ref(*args, **kwargs) def _print_process_output(self, process, input_, capture, realtime): """Reads 'input_' until process is terminated. Writes 'input_' content to 'capture' (string) and 'realtime' stream """ if self.buffering: read = lambda: input_.read(self.buffering) else: # line buffered read = lambda: input_.readline() while True: try: line = read().decode(self.encoding, self.decode_error) except Exception: # happens when fails to decoded input process.terminate() input_.read() raise if not line: break capture.write(line) if realtime: realtime.write(line) realtime.flush() # required if on byte buffering mode def execute(self, out=None, err=None): """ Execute command action both stdout and stderr from the command are captured and saved on self.out/err. Real time output is controlled by parameters @param out: None - no real time output a file like object (has write method) @param err: idem @return failure: - None: if successful - TaskError: If subprocess return code is greater than 125 - TaskFailed: If subprocess return code isn't zero (and not greater than 125) """ try: action = self.expand_action() except Exception as exc: return TaskError( "CmdAction Error creating command string", exc) # set environ to change output buffering subprocess_pkwargs = self.pkwargs.copy() env = None if 'env' in subprocess_pkwargs: env = subprocess_pkwargs['env'] del subprocess_pkwargs['env'] if self.buffering: if not env: env = os.environ.copy() env['PYTHONUNBUFFERED'] = '1' capture_io = self.task.io.capture if self.task else True if capture_io: p_out = p_err = subprocess.PIPE else: p_out = p_err = None # spawn task process process = subprocess.Popen( action, shell=self.shell, # bufsize=2, # ??? no effect use PYTHONUNBUFFERED instead stdout=p_out, stderr=p_err, env=env, **subprocess_pkwargs) if capture_io: output = StringIO() errput = StringIO() t_out = Thread(target=self._print_process_output, args=(process, process.stdout, output, out)) t_err = Thread(target=self._print_process_output, args=(process, process.stderr, errput, err)) t_out.start() t_err.start() t_out.join() t_err.join() self.out = output.getvalue() self.err = errput.getvalue() self.result = self.out + self.err # make sure process really terminated process.wait() # task error - based on: # http://www.gnu.org/software/bash/manual/bashref.html#Exit-Status # it doesnt make so much difference to return as Error or Failed anyway if process.returncode > 125: return TaskError("Command error: '%s' returned %s" % (action, process.returncode)) # task failure if process.returncode != 0: return TaskFailed("Command failed: '%s' returned %s" % (action, process.returncode)) # save stdout in values if self.save_out: self.values[self.save_out] = self.out def expand_action(self): """Expand action using task meta informations if action is a string. Convert `Path` elements to `str` if action is a list. @returns: string -> expanded string if action is a string list - string -> expanded list of command elements """ if not self.task: return self.action # cant expand keywords if action is a list of strings if isinstance(self.action, list): action = [] for element in self.action: if isinstance(element, str): action.append(element) elif isinstance(element, PurePath): action.append(str(element)) else: msg = ("%s. CmdAction element must be a str " "or Path from pathlib. Got '%r' (%s)") raise InvalidTask( msg % (self.task.name, element, type(element))) return action subs_dict = { 'targets': " ".join(self.task.targets), 'dependencies': " ".join(self.task.file_dep), } # dep_changed is set on get_status() # Some commands (like `clean` also uses expand_args but do not # uses get_status, so `changed` is not available. if self.task.dep_changed is not None: subs_dict['changed'] = " ".join(self.task.dep_changed) # task option parameters subs_dict.update(self.task.options) # convert positional parameters from list space-separated string if self.task.pos_arg: if self.task.pos_arg_val: pos_val = ' '.join(self.task.pos_arg_val) else: pos_val = '' subs_dict[self.task.pos_arg] = pos_val if self.STRING_FORMAT == 'old': return self.action % subs_dict elif self.STRING_FORMAT == 'new': return self.action.format(**subs_dict) else: assert self.STRING_FORMAT == 'both' return self.action.format(**subs_dict) % subs_dict def __str__(self): return "Cmd: %s" % self._action def __repr__(self): return "" % str(self._action) class Writer(object): """Write to N streams. This is used on python-actions to allow the stream to be output to terminal and captured at the same time. """ def __init__(self, *writers): """@param writers - file stream like objects""" self.writers = [] self.orig_stream = None # The original stream terminal/file for writer in writers: self.add_writer(writer) def add_writer(self, stream, *, is_original=False): """adds a stream to the list of writers @param is: (bool) if specified overwrites real isatty from stream """ self.writers.append(stream) if is_original: self.orig_stream = stream def write(self, text): """write 'text' to all streams""" for stream in self.writers: stream.write(text) def flush(self): """flush all streams""" for stream in self.writers: stream.flush() def isatty(self): if self.orig_stream: return self.orig_stream.isatty() return False def fileno(self): if self.orig_stream: return self.orig_stream.fileno() raise io.UnsupportedOperation() class PythonAction(BaseAction): """Python action. Execute a python callable. @ivar py_callable: (callable) Python callable @ivar args: (sequence) Extra arguments to be passed to py_callable @ivar kwargs: (dict) Extra keyword arguments to be passed to py_callable @ivar task(Task): reference to task that contains this action @ivar pm_pdb: if True drop into PDB on exception when executing task """ pm_pdb = False def __init__(self, py_callable, args=None, kwargs=None, task=None): # pylint: disable=W0231 self.py_callable = py_callable self.task = task self.out = None self.err = None self.result = None self.values = {} if args is None: self.args = [] else: self.args = args if kwargs is None: self.kwargs = {} else: self.kwargs = kwargs # check valid parameters if not hasattr(self.py_callable, '__call__'): msg = "%r PythonAction must be a 'callable' got %r." raise InvalidTask(msg % (self.task, self.py_callable)) if inspect.isclass(self.py_callable): msg = "%r PythonAction can not be a class got %r." raise InvalidTask(msg % (self.task, self.py_callable)) if inspect.isbuiltin(self.py_callable): msg = "%r PythonAction can not be a built-in got %r." raise InvalidTask(msg % (self.task, self.py_callable)) if type(self.args) is not tuple and type(self.args) is not list: msg = "%r args must be a 'tuple' or a 'list'. got '%s'." raise InvalidTask(msg % (self.task, self.args)) if type(self.kwargs) is not dict: msg = "%r kwargs must be a 'dict'. got '%s'" raise InvalidTask(msg % (self.task, self.kwargs)) def _prepare_kwargs(self): return BaseAction._prepare_kwargs(self.task, self.py_callable, self.args, self.kwargs) def execute(self, out=None, err=None): """Execute command action both stdout and stderr from the command are captured and saved on self.out/err. Real time output is controlled by parameters @param out: None - no real time output a file like object (has write method) @param err: idem @return failure: see CmdAction.execute """ capture_io = self.task.io.capture if self.task else True if capture_io: # set std stream old_stdout = sys.stdout output = StringIO() out_writer = Writer() # capture output but preserve isatty() from original stream out_writer.add_writer(output) if out: out_writer.add_writer(out, is_original=True) sys.stdout = out_writer old_stderr = sys.stderr errput = StringIO() err_writer = Writer() err_writer.add_writer(errput) if err: err_writer.add_writer(err, is_original=True) sys.stderr = err_writer kwargs = self._prepare_kwargs() # execute action / callable try: returned_value = self.py_callable(*self.args, **kwargs) except Exception as exception: if self.pm_pdb: # pragma: no cover # start post-mortem debugger deb = pdb.Pdb(stdin=sys.__stdin__, stdout=sys.__stdout__) deb.reset() deb.interaction(None, sys.exc_info()[2]) return TaskError("PythonAction Error", exception) finally: # restore std streams /log captured streams if capture_io: sys.stdout = old_stdout sys.stderr = old_stderr self.out = output.getvalue() self.err = errput.getvalue() # if callable returns false. Task failed if returned_value is False: return TaskFailed("Python Task failed: '%s' returned %s" % (self.py_callable, returned_value)) elif returned_value is True or returned_value is None: pass elif isinstance(returned_value, str): self.result = returned_value elif isinstance(returned_value, dict): self.values = returned_value self.result = returned_value elif isinstance(returned_value, (TaskFailed, TaskError)): return returned_value else: return TaskError("Python Task error: '%s'. It must return:\n" "False for failed task.\n" "True, None, string or dict for successful task\n" "returned %s (%s)" % (self.py_callable, returned_value, type(returned_value))) def __str__(self): # get object description excluding runtime memory address return "Python: %s" % str(self.py_callable)[1:].split(' at ')[0] def __repr__(self): return "" % (repr(self.py_callable)) def create_action(action, task_ref, param_name): """ Create action using proper constructor based on the parameter type @param action: Action to be created @type action: L{BaseAction} subclass object, str, tuple or callable @param task_ref: Task object this action belongs to @param param_name: str, name of task param. i.e actions, teardown, clean @raise InvalidTask: If action parameter type isn't valid """ if isinstance(action, BaseAction): action.task = task_ref return action if isinstance(action, str): return CmdAction(action, task_ref, shell=True) if isinstance(action, list): return CmdAction(action, task_ref, shell=False) if isinstance(action, tuple): if len(action) > 3: msg = "Task '{}': invalid '{}' tuple length. got: {!r} {}".format( task_ref.name, param_name, action, type(action)) raise InvalidTask(msg) py_callable, args, kwargs = (list(action) + [None] * (3 - len(action))) return PythonAction(py_callable, args, kwargs, task_ref) if hasattr(action, '__call__'): return PythonAction(action, task=task_ref) msg = "Task '{}': invalid '{}' type. got: {!r} {}".format( task_ref.name, param_name, action, type(action)) raise InvalidTask(msg) doit-0.36.0/doit/api.py000066400000000000000000000031271423054503100146210ustar00rootroot00000000000000"""APIs to execute doit in non standard way. - run(): shortcut to get tasks from current module/dict (instead of dodo.py). - run_tasks(): to be used by custom CLIs, run tasks without CLI parsing. """ import sys from doit.cmdparse import CmdParseError from doit.exceptions import InvalidDodoFile, InvalidCommand, InvalidTask from doit.cmd_base import ModuleTaskLoader, get_loader from doit.doit_cmd import DoitMain def run(task_creators): """run doit using task_creators @param task_creators: module or dict containing task creators """ sys.exit(DoitMain(ModuleTaskLoader(task_creators)).run(sys.argv[1:])) def run_tasks(loader, tasks, extra_config=None): """run DoitMain instance with speficied tasks and parameters :params tasks: list of task names (str) """ loader.task_opts = tasks # task_opts will be used as @task_param main = DoitMain(loader, extra_config=extra_config) task_names = list(tasks.keys()) # get list of available commands sub_cmds = main.get_cmds() task_loader = get_loader(main.config, main.task_loader, sub_cmds) # execute command cmd_name = 'run' command = sub_cmds.get_plugin(cmd_name)( task_loader=task_loader, config=main.config, bin_name=main.BIN_NAME, cmds=sub_cmds, opt_vals={}, ) try: return command.parse_execute(task_names) except (CmdParseError, InvalidDodoFile, InvalidCommand, InvalidTask) as err: if isinstance(err, InvalidCommand): err.cmd_used = cmd_name err.bin_name = main.BIN_NAME raise err doit-0.36.0/doit/cmd_base.py000066400000000000000000000502261423054503100156070ustar00rootroot00000000000000import inspect import sys from collections import deque from collections import defaultdict import textwrap from .globals import Globals from . import version from .cmdparse import CmdOption, CmdParse from .exceptions import InvalidCommand, InvalidDodoFile from .dependency import CHECKERS, DbmDB, JsonDB, SqliteDB, Dependency, JSONCodec from .action import CmdAction from .plugin import PluginDict from . import loader def version_tuple(ver_in): """convert a version string or tuple into a 3-element tuple with ints Any part that is not a number (dev0, a2, b4) will be converted to -1 """ result = [] if isinstance(ver_in, str): parts = ver_in.split('.') else: parts = ver_in for rev in parts: try: result.append(int(rev)) except ValueError: result.append(-1) assert len(result) == 3 return result def _wrap(content, indent_level): """wrap multiple lines keeping the indentation""" indent = ' ' * indent_level wrap_opt = { 'initial_indent': indent, 'subsequent_indent': indent, } lines = [] for paragraph in content.splitlines(): if not paragraph: lines.append('') continue lines.extend(textwrap.wrap(paragraph, **wrap_opt)) return lines class Command(object): """third-party should subclass this for commands that do no use tasks :cvar name: (str) name of sub-cmd to be use from cmdline :cvar doc_purpose: (str) single line cmd description :cvar doc_usage: (str) describe accepted parameters :cvar doc_description: (str) long description/help for cmd :cvar cmd_options: (list of dict) see cmdparse.CmdOption for dict format """ # if not specified uses the class name name = None # doc attributes, should be sub-classed doc_purpose = '' doc_usage = '' doc_description = None # None value will completely omit line from doc # sequence of dicts cmd_options = tuple() # `execute_tasks` indicates whether this command execute task's actions. # This is used by the loader to indicate when delayed task creation # should be used. execute_tasks = False def __init__(self, config=None, bin_name='doit', opt_vals=None, **kwargs): """configure command :param bin_name: str - name of command line program :param config: dict Set extra configuration values, this vals can come from: * directly passed when using the API - through DoitMain.run() * from an INI configuration file """ self.bin_name = bin_name self.name = self.get_name() # config includes all option values and plugins self.config = config if config else {} self._cmdparser = None # option values (i.e. loader options) self.opt_vals = opt_vals if opt_vals else {} # config_vals contains cmd option values self.config_vals = {} if 'GLOBAL' in self.config: self.config_vals.update(self.config['GLOBAL']) if self.name in self.config: self.config_vals.update(self.config[self.name]) # Use post-mortem PDB in case of error loading tasks. # Only available for `run` command. self.pdb = False @classmethod def get_name(cls): """get command name as used from command line""" return cls.name or cls.__name__.lower() @property def cmdparser(self): """get CmdParser instance for this command initialize option values: - Default are taken from harded option definition - Defaults are overwritten from user's cfg (INI) file """ if not self._cmdparser: self._cmdparser = CmdParse(self.get_options()) self._cmdparser.overwrite_defaults(self.config_vals) return self._cmdparser def get_options(self): """@reutrn list of CmdOption """ return [CmdOption(opt) for opt in self.cmd_options] def execute(self, opt_values, pos_args): # pragma: no cover """execute command :param opt_values: (dict) with cmd_options values :param pos_args: (list) of cmd-line positional arguments """ raise NotImplementedError() def parse_execute(self, in_args): """helper. just parse parameters and execute command @args: see method parse @returns: result of self.execute """ params, args = self.cmdparser.parse(in_args) self.pdb = params.get('pdb', False) params.update(self.opt_vals) return self.execute(params, args) def help(self): """return help text""" text = [] text.append("PURPOSE") text.extend(_wrap(self.doc_purpose, 4)) text.append("\nUSAGE") usage = "{} {} {}".format(self.bin_name, self.name, self.doc_usage) text.extend(_wrap(usage, 4)) text.append("\nOPTIONS") options = defaultdict(list) for opt in self.cmdparser.options: options[opt.section].append(opt) for section, opts in sorted(options.items()): section_name = '\n{}'.format(section or self.name) text.extend(_wrap(section_name, 4)) for opt in opts: # ignore option that cant be modified on cmd line if not (opt.short or opt.long): continue text.extend(_wrap(opt.help_param(), 6)) # TODO It should always display option's default value opt_help = opt.help % {'default': opt.default} opt_choices = opt.help_choices() opt_config = 'config: {}'.format(opt.name) if opt.env_var: opt_env = ', environ: {}'.format(opt.env_var) else: opt_env = '' desc = '{} {} ({}{})'.format(opt_help, opt_choices, opt_config, opt_env) text.extend(_wrap(desc, 12)) # print bool inverse option if opt.inverse: text.extend(_wrap('--{}'.format(opt.inverse), 6)) text.extend(_wrap('opposite of --{}'.format(opt.long), 12)) if self.doc_description is not None: text.append("\n\nDESCRIPTION") text.extend(_wrap(self.doc_description, 4)) return "\n".join(text) ###################################################################### # choose internal dependency file. opt_depfile = { 'section': 'DB backend', 'name': 'dep_file', 'short': '', 'long': 'db-file', 'type': str, 'default': ".doit.db", 'help': "file used to save successful runs [default: %(default)s]" } # dependency file DB backend opt_backend = { 'section': 'DB backend', 'name': 'backend', 'short': '', 'long': 'backend', 'type': str, 'default': "dbm", 'help': ("Select dependency file backend. [default: %(default)s]") } # dependency file codecs opt_codec = { 'section': 'doit core', 'name': 'codec_cls', 'short': '', 'long': '', 'type': str, 'default': "json", 'help': ("Select codec for task's data in database. [default: %(default)s]") } opt_check_file_uptodate = { 'section': 'doit core', 'name': 'check_file_uptodate', 'short': '', 'long': 'check_file_uptodate', 'type': str, 'default': 'md5', 'help': """\ Choose how to check if files have been modified. Available options [default: %(default)s]: 'md5': use the md5sum 'timestamp': use the timestamp """ } #### options related to dodo.py # select dodo file containing tasks opt_dodo = { 'section': 'task loader', 'name': 'dodoFile', 'short': 'f', 'long': 'file', 'type': str, 'default': 'dodo.py', 'env_var': 'DOIT_FILE', 'help': "load task from dodo FILE [default: %(default)s]" } # cwd opt_cwd = { 'section': 'task loader', 'name': 'cwdPath', 'short': 'd', 'long': 'dir', 'type': str, 'default': None, 'help': ("set path to be used as cwd directory " "(file paths on dodo file are relative to dodo.py location).") } # seek dodo file on parent folders opt_seek_file = { 'section': 'task loader', 'name': 'seek_file', 'short': 'k', 'long': 'seek-file', 'type': bool, 'default': False, 'env_var': 'DOIT_SEEK_FILE', 'help': ("seek dodo file on parent folders [default: %(default)s]") } class TaskLoader(): def __init__(self): raise NotImplementedError( 'doit.cmd_base.py:TaskLoader was removed on 0.36.0, use TaskLoader2 instead') class TaskLoader2(): """Interface of task loaders with new-style API. :cvar cmd_options: (list of dict) see cmdparse.CmdOption for dict format This API separates the loading of the configuration and the loading of the actual tasks, which enables additional elements to be available during task creation. """ API = 2 cmd_options = () def __init__(self): # list of command names, used to detect clash of task names and commands self.cmd_names = [] self.config = None # reference to config object taken from Command self.task_opts = None # dict with task options (no need parsing, API usage) def setup(self, opt_values): """Delayed initialization. To be implemented if the data is needed by derived classes. :param opt_values: (dict) with values for cmd_options """ pass def load_doit_config(self): """Load doit configuration. The method must not be called before invocation of ``setup``. :return: (dict) Dictionary of doit configuration values. """ raise NotImplementedError() # pragma: no cover def load_tasks(self, cmd, pos_args): """Load tasks. The method must not be called before invocation of ``load_doit_config``. :param cmd: (doit.cmd_base.Command) current command being executed :param pos_args: (list str) positional arguments from command line :return: (List[Task]) """ raise NotImplementedError() # pragma: no cover class NamespaceTaskLoader(TaskLoader2): """Implementation of a loader of tasks from an abstract namespace. A namespace is simply a dictionary to objects like functions and objects. See the derived classes for some concrete namespace types. """ def __init__(self): super().__init__() self.namespace = None def load_doit_config(self): return loader.load_doit_config(self.namespace) def load_tasks(self, cmd, pos_args): tasks = loader.load_tasks( self.namespace, self.cmd_names, allow_delayed=cmd.execute_tasks, args=pos_args, config=self.config, task_opts=self.task_opts) # Add task options from config, if present if self.config is not None: for task in tasks: task_stanza = 'task:' + task.name if task_stanza in self.config: task.cfg_values = self.config[task_stanza] # add values from API run_tasks() usage if self.task_opts is not None: for task in tasks: if self.task_opts and task.name in self.task_opts: task.cfg_values = self.task_opts[task.name] if task.pos_arg and task.pos_arg in task.cfg_values: task.pos_arg_val = task.cfg_values[task.pos_arg] return tasks class ModuleTaskLoader(NamespaceTaskLoader): """load tasks from a module/dictionary containing task generators Usage: `ModuleTaskLoader(my_module)` or `ModuleTaskLoader(globals())` """ def __init__(self, mod_dict): super().__init__() if inspect.ismodule(mod_dict): self.namespace = dict(inspect.getmembers(mod_dict)) else: self.namespace = mod_dict class DodoTaskLoader(NamespaceTaskLoader): """default task-loader create tasks from a dodo.py file""" cmd_options = (opt_dodo, opt_cwd, opt_seek_file) def setup(self, opt_values): # lazily load namespace from dodo file per config parameters: self.namespace = dict(inspect.getmembers(loader.get_module( opt_values['dodoFile'], opt_values['cwdPath'], opt_values['seek_file'], ))) def get_loader(config, task_loader=None, cmds=None): """get task loader and configure it :param config: (dict) the whole config from INI :param task_loader: a TaskLoader class :param cmds: dict of available commands """ config = config if config else {} loader = None if task_loader: loader = task_loader # task_loader set from the API else: global_config = config.get('GLOBAL', {}) if 'loader' in global_config: # a plugin loader loader_name = global_config['loader'] plugins = PluginDict() plugins.add_plugins(config, 'LOADER') loader = plugins.get_plugin(loader_name)() if not loader: loader = DodoTaskLoader() # default loader if cmds: loader.cmd_names = list(sorted(cmds.keys())) loader.config = config return loader ################################ class DoitCmdBase(Command): """ subclass must define: cmd_options => list of option dictionary (see CmdOption) _execute => method, argument names must be option names """ base_options = (opt_depfile, opt_backend, opt_codec, opt_check_file_uptodate) def __init__(self, task_loader, cmds=None, **kwargs): super(DoitCmdBase, self).__init__(**kwargs) self.sel_tasks = None # selected tasks for command self.sel_default_tasks = True # False if tasks were specified from command line self.dep_manager = None self.outstream = sys.stdout self.loader = task_loader self._backends = self.get_backends() def get_options(self): """from base class - merge base_options, loader_options and cmd_options """ opt_list = (self.base_options + self.loader.cmd_options + self.cmd_options) return [CmdOption(opt) for opt in opt_list] def _execute(self): # pragma: no cover """to be subclassed - actual command implementation""" raise NotImplementedError @staticmethod def check_minversion(minversion): """check if this version of doit satisfy minimum required version Minimum version specified by configuration on dodo. """ if minversion: if version_tuple(minversion) > version_tuple(version.VERSION): msg = ('Please update doit. ' 'Minimum version required is {required}. ' 'You are using {actual}. ') raise InvalidDodoFile(msg.format(required=minversion, actual=version.VERSION)) def get_checker_cls(self, check_file_uptodate): """return checker class to be used by dep_manager""" if isinstance(check_file_uptodate, str): if check_file_uptodate not in CHECKERS: msg = ("No check_file_uptodate named '{}'." " Type '{} help run' to see a list " "of available checkers.").format( check_file_uptodate, self.bin_name) raise InvalidCommand(msg) return CHECKERS[check_file_uptodate] else: # user defined class return check_file_uptodate def get_codec_cls(self, codec): """return a class used to encode or decode python-action results""" if isinstance(codec, str): if codec == 'json': return JSONCodec else: # pragma: no cover raise NotImplementedError('Implement codec plugin') else: # user specified class return codec def get_backends(self): """return PluginDict of DB backends, including core and plugins""" backend_map = {'dbm': DbmDB, 'json': JsonDB, 'sqlite3': SqliteDB} # add plugins plugins = PluginDict() plugins.add_plugins(self.config, 'BACKEND') backend_map.update(plugins.to_dict()) # set choices, sub-classes might not have this option if 'backend' in self.cmdparser: choices = {k: getattr(v, 'desc', '') for k, v in backend_map.items()} self.cmdparser['backend'].choices = choices return backend_map def execute(self, params, args): """load dodo.py, set attributes and call self._execute :param params: instance of cmdparse.DefaultUpdate :param args: list of string arguments (containing task names) """ self.loader.setup(params) dodo_config = self.loader.load_doit_config() # merge config values from dodo.py into params params.update_defaults(dodo_config) self.check_minversion(params.get('minversion')) # set selected tasks for command self.sel_default_tasks = len(args) == 0 self.sel_tasks = args or params.get('default_tasks') CmdAction.STRING_FORMAT = params.get('action_string_formatting', 'old') if CmdAction.STRING_FORMAT not in ('old', 'both', 'new'): raise InvalidDodoFile( '`action_string_formatting` must be one of `old`, `both`, `new`') # create dep manager db_class = self._backends.get(params['backend']) checker_cls = self.get_checker_cls(params['check_file_uptodate']) codec_cls = self.get_codec_cls(params['codec_cls']) # note the command have the responsibility to call dep_manager.close() if self.dep_manager is None: # dep_manager might have been already set (used on unit-test) self.dep_manager = Dependency( db_class, params['dep_file'], checker_cls=checker_cls, codec_cls=codec_cls) # register dependency manager in global registry: Globals.dep_manager = self.dep_manager # load tasks self.task_list = self.loader.load_tasks(cmd=self, pos_args=args) # hack to pass parameter into _execute() calls that are not part # of command line options params['pos_args'] = args params['continue_'] = params.get('continue') # hack: determine if value came from command line or config params['force_verbosity'] = 'verbosity' in params._non_default_keys # magic - create dict based on signature of _execute() method. # this done so that _execute() have a nice API with name parameters # instead of just taking a dict. args_name = list(inspect.signature(self._execute).parameters.keys()) exec_params = dict((n, params[n]) for n in args_name) return self._execute(**exec_params) # helper functions to find list of tasks def check_tasks_exist(tasks, name_list, skip_wildcard=False): """check task exist""" if not name_list: return for task_name in name_list: if skip_wildcard and '*' in task_name: continue if task_name not in tasks: msg = "'%s' is not a task." raise InvalidCommand(msg % task_name) # this is used by commands that do not execute tasks (forget, auto...) def tasks_and_deps_iter(tasks, sel_tasks, yield_duplicates=False): """iterator of select_tasks and its dependencies @param tasks (dict - Task) @param sel_tasks(list - str) """ processed = set() # str - task name to_process = deque(sel_tasks) # str - task name # get initial task while to_process: task = tasks[to_process.popleft()] processed.add(task.name) yield task # FIXME this does not take calc_dep into account for task_dep in task.task_dep + task.setup_tasks: if (task_dep not in processed) and (task_dep not in to_process): to_process.append(task_dep) elif yield_duplicates: yield tasks[task_dep] def subtasks_iter(tasks, task): """find all subtasks for a given task @param tasks (dict - Task) @param task (Task) """ for name in task.task_dep: dep = tasks[name] if dep.subtask_of == task.name: yield dep doit-0.36.0/doit/cmd_clean.py000066400000000000000000000137371423054503100157650ustar00rootroot00000000000000import fnmatch from collections import OrderedDict from .control import TaskControl from .cmd_base import DoitCmdBase from .cmd_base import check_tasks_exist opt_clean_dryrun = { 'name': 'dryrun', 'short': 'n', # like make dry-run 'long': 'dry-run', 'type': bool, 'default': False, 'help': 'print actions without really executing them', } opt_clean_cleandep = { 'name': 'cleandep', 'short': 'c', 'long': 'clean-dep', 'type': bool, 'default': False, 'help': 'clean task dependencies too', } opt_clean_cleanall = { 'name': 'cleanall', 'short': 'a', # all 'long': 'clean-all', 'type': bool, 'default': False, 'help': 'clean all task', } opt_clean_forget = { 'name': 'cleanforget', 'long': 'forget', 'type': bool, 'default': False, 'help': 'also forget tasks after cleaning', } class Clean(DoitCmdBase): doc_purpose = "clean action / remove targets" doc_usage = "[TASK ...]" doc_description = ("If no task is specified clean default tasks and " "set --clean-dep automatically.") cmd_options = (opt_clean_cleandep, opt_clean_cleanall, opt_clean_dryrun, opt_clean_forget) def clean_tasks(self, tasks, dryrun, cleanforget): """ensure task clean-action is executed only once""" cleaned = set() forget_tasks = cleanforget and not dryrun for task in tasks: if task.name not in cleaned: cleaned.add(task.name) task.clean(self.outstream, dryrun) if forget_tasks: self.dep_manager.remove(task.name) self.dep_manager.close() def _expand(self, clean_list): result = [] for name in clean_list: if '*' in name: result.extend(t.name for t in self.task_list if fnmatch.fnmatch(t.name, name)) else: result.append(name) return result def _execute(self, dryrun, cleandep, cleanall, cleanforget, pos_args=None): """Clean tasks @param task_list (list - L{Task}): list of all tasks from dodo file @ivar dryrun (bool): if True clean tasks are not executed (just print out what would be executed) @param cleandep (bool): execute clean from task_dep @param cleanall (bool): clean all tasks @param cleanforget (bool): forget cleaned tasks @var default_tasks (list - string): list of default tasks @var selected_tasks (list - string): list of tasks selected from cmd-line """ tasks = TaskControl(self.task_list).tasks # behavior of cleandep is different if selected_tasks comes from # command line or DOIT_CONFIG.default_tasks selected_tasks = pos_args check_tasks_exist(tasks, selected_tasks, skip_wildcard=True) # get base list of tasks to be cleaned if selected_tasks and not cleanall: # from command line clean_list = self._expand(selected_tasks) else: # if not cleaning specific task enable clean_dep automatically cleandep = True if self.sel_tasks is not None: clean_list = self._expand(self.sel_tasks) # default tasks from config else: clean_list = [t.name for t in self.task_list] # note: reversing is not required, but helps reversing # execution order even if there are no restrictions about order. clean_list.reverse() tree = CleanDepTree() # include dependencies in list if cleandep: for name in clean_list: tree.build_nodes_with_deps(tasks, name) # include only subtasks in list else: tree.build_nodes(tasks, clean_list) to_clean = [tasks[x] for x in tree.flat()] self.clean_tasks(to_clean, dryrun, cleanforget) class CleanDepTree: """Create node structure where each node is a task and its children are tasks that has the node as a task_dep/setup_task. This creates an upside-down tree where leaf nodes should be the first ones to be "cleaned". """ def __init__(self): self.nodes = OrderedDict() self._processed = set() # task names that were already built def build_nodes_with_deps(self, tasks, task_name): """build node including task_dep's""" if task_name in self._processed: return else: self._processed.add(task_name) # add node itself if not in list of nodes self.nodes.setdefault(task_name, []) task = tasks[task_name] # reversing not required for dep_name in reversed(task.setup_tasks + task.task_dep): rev_dep = self.nodes.setdefault(dep_name, []) rev_dep.append(task_name) self.build_nodes_with_deps(tasks, dep_name) def build_nodes(self, tasks, clean_list): """build nodes with sub-tasks but no other task_dep""" for name in clean_list: # add node itself if not in list of nodes self.nodes.setdefault(name, []) task = tasks[name] # reversing not required for dep_name in reversed(task.task_dep): if tasks[dep_name].subtask_of == name: rev_dep = self.nodes.setdefault(dep_name, []) rev_dep.append(name) def flat(self): """return list of tasks in the order they should be `clean` """ to_clean = [] while self.nodes: head, children = self.nodes.popitem(0) to_clean.extend([x for x in self._get_leafs(head, children)]) return to_clean def _get_leafs(self, name, children): for child_name in children: if child_name in self.nodes: grand = self.nodes.pop(child_name) yield from self._get_leafs(child_name, grand) yield name doit-0.36.0/doit/cmd_completion.py000066400000000000000000000270461423054503100170520ustar00rootroot00000000000000"""generate shell script with tab completion code for doit commands/tasks""" import sys from string import Template from .exceptions import InvalidCommand from .cmd_base import DoitCmdBase opt_shell = { 'name': 'shell', 'short': 's', 'long': 'shell', 'type': str, 'choices': (('bash', ''), ('zsh', '')), 'default': 'bash', 'help': 'Completion code for SHELL. [default: %(default)s]', } opt_hardcode_tasks = { 'name': 'hardcode_tasks', 'short': '', 'long': 'hardcode-tasks', 'type': bool, 'default': False, 'help': 'Hardcode tasks from current task list.', } class TabCompletion(DoitCmdBase): """generate scripts for tab-completion If hardcode-tasks options is chosen it will get the task list from the current dodo file and include in the completion script. Otherwise the script will dynamically call `doit list` to get the list of tasks. If it is completing a sub-task (contains ':' in the name), it will always call doit while evaluating the options. """ doc_purpose = "generate script for tab-completion" doc_usage = "" doc_description = None cmd_options = (opt_shell, opt_hardcode_tasks, ) def __init__(self, cmds=None, **kwargs): super(TabCompletion, self).__init__(cmds=cmds, **kwargs) self.init_kwargs = kwargs self.init_kwargs['cmds'] = cmds if cmds: self.cmds = cmds.to_dict() # dict name - Command class def execute(self, opt_values, pos_args): if opt_values['shell'] == 'bash': self._generate_bash(opt_values, pos_args) elif opt_values['shell'] == 'zsh': self._generate_zsh(opt_values, pos_args) else: msg = 'Invalid option for --shell "{0}"' raise InvalidCommand(msg.format(opt_values['shell'])) @classmethod def _bash_cmd_args(cls, cmd): """return case item for completion of specific sub-command""" comp = [] if 'TASK' in cmd.doc_usage: comp.append('${tasks}') if 'COMMAND' in cmd.doc_usage: comp.append('${sub_cmds}') if comp: completion = '-W "{0}"'.format(' '.join(comp)) else: completion = '-f' # complete file return bash_subcmd_arg.format(cmd_name=cmd.name, completion=completion) def _generate_bash(self, opt_values, pos_args): # some applications built with doit do not use dodo.py files for opt in self.get_options(): if opt.name == 'dodoFile': get_dodo_part = bash_get_dodo pt_list_param = '--file="$dodof"' break else: get_dodo_part = '' pt_list_param = '' # dict with template values pt_bin_name = sys.argv[0].split('/')[-1] tmpl_vars = { 'pt_bin_name': pt_bin_name, 'pt_cmds': ' '.join(sorted(self.cmds)), 'pt_list_param': pt_list_param, } # if hardcode tasks if opt_values['hardcode_tasks']: if getattr(self.loader, 'API', 1) == 2: self.loader.setup(opt_values) self.loader.load_doit_config() self.task_list = self.loader.load_tasks(cmd=self, pos_args=pos_args) else: self.task_list, _ = self.loader.load_tasks( self, opt_values, pos_args) task_names = (t.name for t in self.task_list if not t.subtask_of) tmpl_vars['pt_tasks'] = '"{0}"'.format(' '.join(sorted(task_names))) else: tmpl_list_cmd = "$({0} list {1} --quiet 2>/dev/null)" tmpl_vars['pt_tasks'] = tmpl_list_cmd.format(pt_bin_name, pt_list_param) # case statement to complete sub-commands cmds_args = [] for name in sorted(self.cmds): cmd_class = self.cmds[name] cmd = cmd_class(**self.init_kwargs) cmds_args.append(self._bash_cmd_args(cmd)) comp_subcmds = ("\n case ${words[1]} in\n" + "".join(cmds_args) + "\n esac\n") template = Template( bash_start + bash_opt_file + get_dodo_part + bash_task_list + bash_first_arg + comp_subcmds + bash_end) self.outstream.write(template.safe_substitute(tmpl_vars)) @staticmethod def _zsh_arg_line(opt): """create a text line for completion of a command arg""" # '(-c|--continue)'{-c,--continue}'[continue executing tasks...]' \ # '--db-file[file used to save successful runs]' \ if opt.short and opt.long: tmpl = ('"(-{0.short}|--{0.long})"{{-{0.short},--{0.long}}}"' '[{help}]" \\') elif not opt.short and opt.long: tmpl = '"--{0.long}[{help}]" \\' elif opt.short and not opt.long: tmpl = '"-{0.short}[{help}]" \\' else: # without short or long options cant be really used return '' ohelp = opt.help.replace(']', r'\]').replace('"', r'\"') return tmpl.format(opt, help=ohelp).replace('\n', ' ') @classmethod def _zsh_arg_list(cls, cmd): """return list of arguments lines for zsh completion""" args = [] for opt in cmd.get_options(): args.append(cls._zsh_arg_line(opt)) if 'TASK' in cmd.doc_usage: args.append("'*::task:(($tasks))'") if 'COMMAND' in cmd.doc_usage: args.append("'::cmd:(($commands))'") return args @classmethod def _zsh_cmd_args(cls, cmd): """create the content for "case" statement with all command options """ arg_lines = cls._zsh_arg_list(cmd) tmpl = """ ({cmd_name}) _command_args=( {args_body} '' ) ;; """ args_body = '\n '.join(arg_lines) return tmpl.format(cmd_name=cmd.name, args_body=args_body) # TODO: # detect correct dodo-file location # complete sub-tasks # task options def _generate_zsh(self, opt_values, pos_args): # deal with doit commands cmds_desc = [] cmds_args = [] for name in sorted(self.cmds): cmd_class = self.cmds[name] cmd = cmd_class(**self.init_kwargs) cmds_desc.append(" '{0}: {1}'".format(cmd.name, cmd.doc_purpose)) cmds_args.append(self._zsh_cmd_args(cmd)) template_vars = { 'pt_bin_name': sys.argv[0].split('/')[-1], 'pt_cmds': '\n '.join(cmds_desc), 'pt_cmds_args': '\n'.join(cmds_args), } if opt_values['hardcode_tasks']: if getattr(self.loader, 'API', 1) == 2: self.loader.setup(opt_values) self.loader.load_doit_config() self.task_list = self.loader.load_tasks(cmd=self, pos_args=pos_args) else: self.task_list, _ = self.loader.load_tasks( self, opt_values, pos_args) lines = [] for task in self.task_list: if not task.subtask_of: lines.append("'{0}: {1}'".format(task.name, task.doc)) template_vars['pt_tasks'] = '(\n{0}\n)'.format( '\n'.join(sorted(lines))) else: tmp_tasks = Template( '''("${(f)$($pt_bin_name list --template '{name}: {doc}')}")''') template_vars['pt_tasks'] = tmp_tasks.safe_substitute(template_vars) template = Template(zsh_start) self.outstream.write(template.safe_substitute(template_vars)) ############## templates # Variables starting with 'pt_' belongs to the Python Template # to generate the script. # Remaining are shell variables used in the script. ################################################################ ############### bash template bash_start = """# bash completion for $pt_bin_name # auto-generate by `$pt_bin_name tabcompletion` # to activate it you need to 'source' the generate script # $ source # reference => http://www.debian-administration.org/articles/317 # patch => http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=711879 _$pt_bin_name() { local cur prev words cword basetask sub_cmds tasks i dodof COMPREPLY=() # contains list of words with suitable completion # remove colon from word separator list because doit uses colon on task names _get_comp_words_by_ref -n : cur prev words cword # list of sub-commands sub_cmds="$pt_cmds" """ # FIXME - wont be necessary after adding support for options with type bash_opt_file = """ # options that take file/dir as values should complete file-system if [[ "$prev" == "-f" || "$prev" == "-d" || "$prev" == "-o" ]]; then _filedir return 0 fi if [[ "$cur" == *=* ]]; then prev=${cur/=*/} cur=${cur/*=/} if [[ "$prev" == "--file=" || "$prev" == "--dir=" || "$prev" == "--output-file=" ]]; then _filedir -o nospace return 0 fi fi """ bash_get_dodo = """ # get name of the dodo file for (( i=0; i < ${#words[@]}; i++)); do case "${words[i]}" in -f) dodof=${words[i+1]} break ;; --file=*) dodof=${words[i]/*=/} break ;; esac done # dodo file not specified, use default if [ ! $dodof ] then dodof="dodo.py" fi """ bash_task_list = """ # get task list # if it there is colon it is getting a subtask, complete only subtask names if [[ "$cur" == *:* ]]; then # extract base task name (remove everything after colon) basetask=${cur%:*} # sub-tasks tasks=$($pt_bin_name list $pt_list_param --quiet --all ${basetask} 2>/dev/null) COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) ) __ltrim_colon_completions "$cur" return 0 # without colons get only top tasks else tasks=$pt_tasks fi """ bash_first_arg = """ # match for first parameter must be sub-command or task # FIXME doit accepts options "-" in the first parameter but we ignore this case if [[ ${cword} == 1 ]] ; then COMPREPLY=( $(compgen -W "${sub_cmds} ${tasks}" -- ${cur}) ) return 0 fi """ bash_subcmd_arg = """ {cmd_name}) COMPREPLY=( $(compgen {completion} -- $cur) ) return 0 ;;""" bash_end = """ # if there is already one parameter match only tasks (no commands) COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) ) } complete -o filenames -F _$pt_bin_name $pt_bin_name """ ################################################################ ############### zsh template zsh_start = """#compdef $pt_bin_name _$pt_bin_name() { local -a commands tasks # format is 'completion:description' commands=( $pt_cmds ) # split output by lines to create an array tasks=$pt_tasks # complete command or task name if (( CURRENT == 2 )); then _arguments -A : '::cmd:(($commands))' '::task:(($tasks))' return fi # revome program name from $words and decrement CURRENT local curcontext context state state_desc line _arguments -C '*:: :->' # complete sub-command or task options local -a _command_args case "$words[1]" in $pt_cmds_args # default completes task names (*) _command_args='*::task:(($tasks))' ;; esac # -A no options will be completed after the first non-option argument _arguments -A : $_command_args return 0 } _$pt_bin_name """ doit-0.36.0/doit/cmd_dumpdb.py000066400000000000000000000030021423054503100161360ustar00rootroot00000000000000import pprint import json import dbm from dbm import whichdb from .exceptions import InvalidCommand from .cmd_base import Command, opt_depfile def dbm_iter(db): # try dictionary interface - used in dumbdb try: return db.items() except AttributeError: # pragma: no cover pass # try firstkey/nextkey - ok for py3 dbm.gnu try: # pragma: no cover db.firstkey def iter_gdbm(db): k = db.firstkey() while k is not None: yield k, db[k] k = db.nextkey(k) return iter_gdbm(db) except Exception: # pragma: no cover raise InvalidCommand("It seems your DB backend doesn't support " "iterating through all elements") class DumpDB(Command): """dump dependency DB""" doc_purpose = 'dump dependency DB' doc_usage = '' doc_description = None cmd_options = (opt_depfile,) def execute(self, opt_values, pos_args): dep_file = opt_values['dep_file'] db_type = whichdb(dep_file) print("DBM type is '%s'" % db_type) if db_type in ('dbm', 'dbm.ndbm'): # pragma: no cover raise InvalidCommand('ndbm does not support iteration of elements') data = dbm.open(dep_file) for key, value_str in dbm_iter(data): value_dict = json.loads(value_str.decode('utf-8')) value_fmt = pprint.pformat(value_dict, indent=4, width=100) print("{key} -> {value}".format(key=key, value=value_fmt)) doit-0.36.0/doit/cmd_forget.py000066400000000000000000000041651423054503100161640ustar00rootroot00000000000000from .cmd_base import DoitCmdBase, check_tasks_exist from .cmd_base import tasks_and_deps_iter, subtasks_iter opt_forget_taskdep = { 'name': 'forget_sub', 'short': 's', 'long': 'follow-sub', 'type': bool, 'default': False, 'help': 'forget task dependencies too', } opt_disable_default = { 'name': 'forget_disable_default', 'long': 'disable-default', 'inverse': 'enable-default', 'type': bool, 'default': False, 'help': 'disable forgetting default tasks (when no arguments are passed)', } opt_forget_all = { 'name': 'forget_all', 'short': 'a', 'long': 'all', 'type': bool, 'default': False, 'help': 'forget all tasks', } class Forget(DoitCmdBase): doc_purpose = "clear successful run status from internal DB" doc_usage = "[TASK ...]" doc_description = None cmd_options = (opt_forget_taskdep, opt_disable_default, opt_forget_all) def _execute(self, forget_sub, forget_disable_default, forget_all): """remove saved data successful runs from DB """ if forget_all: self.dep_manager.remove_all() self.outstream.write("forgetting all tasks\n") elif self.sel_default_tasks and forget_disable_default: self.outstream.write( "no tasks specified, pass task name, --enable-default or --all\n") # forget tasks from list else: tasks = dict([(t.name, t) for t in self.task_list]) check_tasks_exist(tasks, self.sel_tasks) forget_list = self.sel_tasks if forget_sub: to_forget = list(tasks_and_deps_iter(tasks, forget_list, True)) else: to_forget = [] for name in forget_list: task = tasks[name] to_forget.append(task) to_forget.extend(subtasks_iter(tasks, task)) for task in to_forget: # forget it - remove from dependency file self.dep_manager.remove(task.name) self.outstream.write("forgetting %s\n" % task.name) self.dep_manager.close() doit-0.36.0/doit/cmd_help.py000066400000000000000000000127331423054503100156260ustar00rootroot00000000000000from .exceptions import InvalidDodoFile from .cmdparse import TaskParse, CmdOption from .cmd_base import DoitCmdBase HELP_TASK = """ Task Dictionary parameters -------------------------- Tasks are defined by functions starting with the string ``task_``. It must return a dictionary describing the task with the following fields: actions [required]: - type: Python-Task -> callable or tuple (callable, `*args`, `**kwargs`) - type: Cmd-Task -> string or list of strings. Shell command. - type: Group-Task -> None. basename: - type: string. if present use it as task name, instead of taking name from python function name [required for sub-task]: - type: string. sub-task identifier file_dep: - type: list. items: * file (string) path relative to the dodo file task_dep: - type: list. items: * task name (string) setup: - type: list. items: * task name (string) targets: - type: list of strings - each item is file-path relative to the dodo file (accepts both files and folders) uptodate: - type: list. items: * None - None values are just ignored * bool - False indicates task is not up-to-date * callable - returns bool or None. must take 2 positional parameters (task, values) calc_dep: - type: list. items: * task name (string) getargs: - type: dictionary * key: string with the name of the function argument (used in a python-action) * value: tuple of (, ) teardown: - type: (list) of actions (see above) doc: - type: string -> the description text clean: - type: (True bool) remove target files - type: (list) of actions (see above) params: - type: (list) of dictionaries containing: - name [required] (string) parameter identifier - default [required] default value for parameter - short [optional] (string - 1 letter) short option string - long [optional] (string) long option string - type [optional] (callable) the option will be converted to this type - env_var [optional] (string) name OS environment variable - choices [optional] (list of 2-tuple str) limit option values, second tuple element is a help description for value - help [optional] (string) description displayed by help command - inverse [optional] (string) for a bool parameter set value to False pos_arg: - type: string -> name of the function argument to receive list of positional arguments verbosity: - type: int - 0: capture (do not print) stdout/stderr from task. - 1: (default) capture stdout only. - 2: do not capture anything (print everything immediately). io: - type: dict - capture (bool): If False task stdout/stderr is not captured/saved internally [default: True] title: - type: callable taking one parameter as argument (the task reference) meta: - type: dict. Extra info from user/plugin not directly used by doit watch: - type: list. items: * (string) path to be watched when using the `auto` command """ class Help(DoitCmdBase): doc_purpose = "show help" doc_usage = "[TASK] [COMMAND]" doc_description = None def __init__(self, cmds=None, **kwargs): """ :param cmds: PluginDict """ self.init_kwargs = kwargs super(Help, self).__init__(cmds=cmds, **kwargs) self._cmds = cmds self.cmds = cmds.to_dict() # dict name - Command class def print_usage(self, cmds): """print doit "usage" (basic help) instructions :var cmds: dict name -> Command class """ print("doit -- automation tool") print("http://pydoit.org") print('') print("Commands") for cmd_name in sorted(cmds.keys()): cmd = cmds[cmd_name] print(" {} {:16s} {}".format( self.bin_name, cmd_name, cmd.doc_purpose)) print("") cmd_help = " {} help".format(self.bin_name) for line in [ "{} show help / reference", "{} task show help on task dictionary fields", "{} show command usage", "{} show task usage"]: print(line.format(cmd_help)) @staticmethod def print_task_help(): """print help for 'task' usage """ print(HELP_TASK) def _execute(self, pos_args): """execute help for specific task""" task_name = pos_args[0] tasks = dict([(t.name, t) for t in self.task_list]) task = tasks.get(task_name, None) if not task: return False print("%s %s" % (task.name, task.doc)) params = list(task.creator_params) + list(task.params) taskcmd = TaskParse([CmdOption(opt) for opt in params]) for opt in taskcmd.options: print("\n".join(opt.help_doc())) return True def execute(self, params, args): """execute cmd 'help' """ if len(args) != 1: self.print_usage(self.cmds) elif args[0] == 'task': self.print_task_help() # help on command elif args[0] in self.cmds: cmd = self.cmds[args[0]](cmds=self._cmds, **self.init_kwargs) print(cmd.help()) else: # help of specific task try: # call base class implementation to run self._execute() if not DoitCmdBase.execute(self, params, args): self.print_usage(self.cmds) except InvalidDodoFile: self.print_usage(self.cmds) return 0 doit-0.36.0/doit/cmd_ignore.py000066400000000000000000000022121423054503100161500ustar00rootroot00000000000000from .cmd_base import DoitCmdBase, check_tasks_exist, subtasks_iter class Ignore(DoitCmdBase): doc_purpose = "ignore task (skip) on subsequent runs" doc_usage = "TASK [TASK ...]" doc_description = None cmd_options = () def _execute(self, pos_args): """mark tasks to be ignored @param ignore_tasks: (list - str) tasks to be ignored. """ ignore_tasks = pos_args # no task specified. if not ignore_tasks: msg = "You cant ignore all tasks! Please select a task.\n" self.outstream.write(msg) return tasks = dict([(t.name, t) for t in self.task_list]) check_tasks_exist(tasks, ignore_tasks) for task_name in ignore_tasks: # for group tasks also remove all tasks from group sub_list = [t.name for t in subtasks_iter(tasks, tasks[task_name])] for to_ignore in [task_name] + sub_list: # ignore it - remove from dependency file self.dep_manager.ignore(tasks[to_ignore]) self.outstream.write("ignoring %s\n" % to_ignore) self.dep_manager.close() doit-0.36.0/doit/cmd_info.py000066400000000000000000000101371423054503100156250ustar00rootroot00000000000000"""command doit info - display info on task metadata""" import pprint from .cmd_base import DoitCmdBase from .exceptions import InvalidCommand opt_hide_status = { 'name': 'hide_status', 'long': 'no-status', 'type': bool, 'default': False, 'help': """Hides reasons why this task would be executed. [default: %(default)s]""" } class Info(DoitCmdBase): """command doit info""" doc_purpose = "show info about a task" doc_usage = "TASK" doc_description = None cmd_options = (opt_hide_status, ) def _execute(self, pos_args, hide_status=False): if len(pos_args) != 1: msg = ('`info` failed, must select *one* task.' '\nCheck `{} help info`.'.format(self.bin_name)) raise InvalidCommand(msg) task_name = pos_args[0] # dict of all tasks tasks = dict([(t.name, t) for t in self.task_list]) printer = pprint.PrettyPrinter(indent=4, stream=self.outstream) task = tasks[task_name] task_attrs = ( ('file_dep', 'list'), ('task_dep', 'list'), ('setup_tasks', 'list'), ('calc_dep', 'list'), ('targets', 'list'), # these fields usually contains reference to python functions # 'actions', 'clean', 'uptodate', 'teardown', 'title' ('getargs', 'dict'), ('params', 'list'), ('verbosity', 'scalar'), ('watch', 'list'), ('meta', 'dict') ) self.outstream.write('\n{}\n'.format(task.name)) if task.doc: self.outstream.write('\n{}\n'.format(task.doc)) # print reason task is not up-to-date retcode = 0 if not hide_status: status = self.dep_manager.get_status(task, tasks, get_log=True) self.outstream.write('\n{:11s}: {}\n' .format('status', status.status)) if status.status != 'up-to-date': # status.status == 'run' or status.status == 'error' self.outstream.write(self.get_reasons(status.reasons)) self.outstream.write('\n') retcode = 1 for (attr, attr_type) in task_attrs: value = getattr(task, attr) # only print fields that have non-empty value if value: self.outstream.write('\n{:11s}: '.format(attr)) if attr_type == 'list': self.outstream.write('\n') for val in value: self.outstream.write(' - {}\n'.format(val)) else: printer.pprint(getattr(task, attr)) return retcode @staticmethod def get_reasons(reasons): '''return string with description of reason task is not up-to-date''' lines = [] if reasons['has_no_dependencies']: lines.append(' * The task has no dependencies.') if reasons['uptodate_false']: lines.append(' * The following uptodate objects evaluate to false:') for utd, utd_args, utd_kwargs in reasons['uptodate_false']: msg = ' - {} (args={}, kwargs={})' lines.append(msg.format(utd, utd_args, utd_kwargs)) if reasons['checker_changed']: msg = ' * The file_dep checker changed from {0} to {1}.' lines.append(msg.format(*reasons['checker_changed'])) sentences = { 'missing_target': 'The following targets do not exist:', 'changed_file_dep': 'The following file dependencies have changed:', 'missing_file_dep': 'The following file dependencies are missing:', 'removed_file_dep': 'The following file dependencies were removed:', 'added_file_dep': 'The following file dependencies were added:', } for reason, sentence in sentences.items(): entries = reasons.get(reason) if entries: lines.append(' * {}'.format(sentence)) for item in entries: lines.append(' - {}'.format(item)) return '\n'.join(lines) doit-0.36.0/doit/cmd_list.py000066400000000000000000000113731423054503100156500ustar00rootroot00000000000000from .cmd_base import DoitCmdBase, check_tasks_exist, subtasks_iter opt_listall = { 'name': 'subtasks', 'short': '', 'long': 'all', 'type': bool, 'default': False, 'help': "list include all sub-tasks from dodo file" } opt_list_quiet = { 'name': 'quiet', 'short': 'q', 'long': 'quiet', 'type': bool, 'default': False, 'help': 'print just task name (less verbose than default)' } opt_list_status = { 'name': 'status', 'short': 's', 'long': 'status', 'type': bool, 'default': False, 'help': 'print task status (R)un, (U)p-to-date, (I)gnored' } opt_list_private = { 'name': 'private', 'short': 'p', 'long': 'private', 'type': bool, 'default': False, 'help': "print private tasks (start with '_')" } opt_list_dependencies = { 'name': 'list_deps', 'short': '', 'long': 'deps', 'type': bool, 'default': False, 'help': ("print list of dependencies " "(file dependencies only)") } opt_template = { 'name': 'template', 'short': '', 'long': 'template', 'type': str, 'default': None, 'help': "display entries with template" } opt_sort = { 'name': 'sort', 'short': '', 'long': 'sort', 'type': str, 'choices': [('name', 'sort by task name'), ('definition', 'list tasks in the order they were defined')], 'default': 'name', 'help': ("choose the manner in which the task list is sorted. " "[default: %(default)s]") } class List(DoitCmdBase): doc_purpose = "list tasks from dodo file" doc_usage = "[TASK ...]" doc_description = None cmd_options = (opt_listall, opt_list_quiet, opt_list_status, opt_list_private, opt_list_dependencies, opt_template, opt_sort) STATUS_MAP = {'ignore': 'I', 'up-to-date': 'U', 'run': 'R', 'error': 'E'} def _print_task(self, template, task, status, list_deps, tasks): """print a single task""" line_data = {'name': task.name, 'doc': task.doc} # FIXME group task status is never up-to-date if status: # FIXME: 'ignore' handling is ugly if self.dep_manager.status_is_ignore(task): task_status = 'ignore' else: task_status = self.dep_manager.get_status(task, tasks).status line_data['status'] = self.STATUS_MAP[task_status] self.outstream.write(template.format(**line_data)) # print dependencies if list_deps: for dep in task.file_dep: self.outstream.write(" - %s\n" % dep) self.outstream.write("\n") @staticmethod def _list_filtered(tasks, filter_tasks, include_subtasks): """return list of task based on selected 'filter_tasks' """ check_tasks_exist(tasks, filter_tasks) # get task by name print_list = [] for name in filter_tasks: task = tasks[name] print_list.append(task) if include_subtasks: print_list.extend(subtasks_iter(tasks, task)) return print_list def _list_all(self, include_subtasks): """list of tasks""" print_list = [] for task in self.task_list: if (not include_subtasks) and task.subtask_of: continue print_list.append(task) return print_list def _execute(self, subtasks=False, quiet=True, status=False, private=False, list_deps=False, template=None, sort='name', pos_args=None): """List task generators""" filter_tasks = pos_args # dict of all tasks tasks = dict([(t.name, t) for t in self.task_list]) if filter_tasks: # list only tasks passed on command line print_list = self._list_filtered(tasks, filter_tasks, subtasks) else: print_list = self._list_all(subtasks) # exclude private tasks if not private: print_list = [t for t in print_list if not t.name.startswith('_')] # set template if template is None: max_name_len = 0 if print_list: max_name_len = max(len(t.name) for t in print_list) template = '{name:<' + str(max_name_len + 3) + '}' if not quiet: template += '{doc}' if status: template = '{status} ' + template template += '\n' # sort list of tasks if sort == 'name': print_list = sorted(print_list) elif sort == 'definition': pass # task list is already sorted in order of definition # print list of tasks for task in print_list: self._print_task(template, task, status, list_deps, tasks) return 0 doit-0.36.0/doit/cmd_resetdep.py000066400000000000000000000055521423054503100165120ustar00rootroot00000000000000from .cmd_base import DoitCmdBase, check_tasks_exist from .cmd_base import subtasks_iter import os class ResetDep(DoitCmdBase): name = "reset-dep" doc_purpose = ("recompute and save the state of file dependencies without " "executing actions") doc_usage = "[TASK ...]" cmd_options = () doc_description = """ This command allows to recompute the information on file dependencies (timestamp, md5sum, ... depending on the ``check_file_uptodate`` setting), and save this in the database, without executing the actions. The command run on all tasks by default, but it is possible to specify a list of tasks to work on. This is useful when the targets of your tasks already exist, and you want doit to consider your tasks as up-to-date. One use-case for this command is when you change the ``check_file_uptodate`` setting, which cause doit to consider all your tasks as not up-to-date. It is also useful if you start using doit while some of your data as already been computed, or when you add a file dependency to a task that has already run. """ def _execute(self, pos_args=None): filter_tasks = pos_args # dict of all tasks tasks = dict([(t.name, t) for t in self.task_list]) # select tasks that command will be applied to if filter_tasks: # list only tasks passed on command line check_tasks_exist(tasks, filter_tasks) # get task by name task_list = [] for name in filter_tasks: task = tasks[name] task_list.append(task) task_list.extend(subtasks_iter(tasks, task)) else: task_list = self.task_list write = self.outstream.write for task in task_list: # Get these now because dep_manager.get_status will remove the task # from the db if the checker changed. values = self.dep_manager.get_values(task.name) result = self.dep_manager.get_result(task.name) missing_deps = [dep for dep in task.file_dep if not os.path.exists(dep)] if len(missing_deps) > 0: deps = "', '".join(missing_deps) write(f"failed {task.name} (Dependent file '{deps}' does not exist.)\n") continue res = self.dep_manager.get_status(task, tasks) # An 'up-to-date' status means that it is useless to recompute the # state: file deps and targets exists, the state has not changed, # there is nothing more to do. if res.status == 'up-to-date': write("skip {}\n".format(task.name)) continue task.values = values self.dep_manager.save_success(task, result_hash=result) write("processed {}\n".format(task.name)) self.dep_manager.close() doit-0.36.0/doit/cmd_run.py000066400000000000000000000172371423054503100155060ustar00rootroot00000000000000import sys import codecs from .exceptions import InvalidCommand from .plugin import PluginDict from .action import PythonAction from .task import Stream from .control import TaskControl from .runner import Runner, MRunner, MThreadRunner from .cmd_base import DoitCmdBase from . import reporter # verbosity opt_verbosity = { 'name': 'verbosity', 'short': 'v', 'long': 'verbosity', 'type': int, 'default': None, 'help': """0 capture (do not print) stdout/stderr from task. 1 capture stdout only. 2 do not capture anything (print everything immediately). [default: 1]""" } # select output file opt_outfile = { 'name': 'outfile', 'short': 'o', 'long': 'output-file', 'type': str, 'default': sys.stdout, 'help': "write output into file [default: stdout]" } # always execute task opt_always = { 'name': 'always', 'short': 'a', 'long': 'always-execute', 'type': bool, 'default': False, 'help': "always execute tasks even if up-to-date [default: %(default)s]", } # continue executing tasks even after a failure opt_continue = { 'name': 'continue', 'short': 'c', 'long': 'continue', 'inverse': 'no-continue', 'type': bool, 'default': False, 'help': ("continue executing tasks even after a failure " "[default: %(default)s]"), } opt_single = { 'name': 'single', 'short': 's', 'long': 'single', 'type': bool, 'default': False, 'help': ("Execute only specified tasks ignoring their task_dep " "[default: %(default)s]"), } opt_num_process = { 'name': 'num_process', 'short': 'n', 'long': 'process', 'type': int, 'default': 0, 'help': "number of subprocesses [default: %(default)s]" } # reporter opt_reporter = { 'name': 'reporter', 'short': 'r', 'long': 'reporter', 'type': str, 'default': 'console', 'help': """Choose output reporter.\n[default: %(default)s]""" } opt_parallel_type = { 'name': 'par_type', 'short': 'P', 'long': 'parallel-type', 'type': str, 'default': 'process', 'help': """Tasks can be executed in parallel in different ways: 'process': uses python multiprocessing module 'thread': uses threads [default: %(default)s] """ } # pdb post-mortem opt_pdb = { 'name': 'pdb', 'short': '', 'long': 'pdb', 'type': bool, 'default': None, 'help': "get into PDB (python debugger) post-mortem in case of unhandled exception" } # use ".*" as default regex for delayed tasks without explicitly specified regex opt_auto_delayed_regex = { 'name': 'auto_delayed_regex', 'short': '', 'long': 'auto-delayed-regex', 'type': bool, 'default': False, 'help': ("""Uses the default regex ".*" for every delayed task loader""" """for which no regex was explicitly defined"""), } opt_report_failure_verbosity = { 'name': 'failure_verbosity', 'short': '', 'long': 'failure-verbosity', 'type': int, 'default': 0, 'help': """Control re-display stdout/stderr for failed tasks on report summary. 0 do not show re-display 1 re-display stderr only 2 re-display both stderr/stdout [default: 0] """ } class Run(DoitCmdBase): doc_purpose = "run tasks" doc_usage = "[TASK/TARGET...]" doc_description = None execute_tasks = True cmd_options = (opt_always, opt_continue, opt_verbosity, opt_reporter, opt_outfile, opt_num_process, opt_parallel_type, opt_pdb, opt_single, opt_auto_delayed_regex, opt_report_failure_verbosity) def __init__(self, **kwargs): super(Run, self).__init__(**kwargs) self.reporters = self.get_reporters() # dict def get_reporters(self): """return dict of all available reporters Also set CmdOption choices. """ # built-in reporters reporters = { 'console': reporter.ConsoleReporter, 'executed-only': reporter.ExecutedOnlyReporter, 'json': reporter.JsonReporter, 'zero': reporter.ZeroReporter, 'error-only': reporter.ErrorOnlyReporter, } # plugins plugins = PluginDict() plugins.add_plugins(self.config, 'REPORTER') reporters.update(plugins.to_dict()) # set choices for reporter cmdoption # sub-classes might not have this option if 'reporter' in self.cmdparser: choices = {k: v.desc for k, v in reporters.items()} self.cmdparser['reporter'].choices = choices return reporters def _execute(self, outfile, verbosity=None, always=False, continue_=False, reporter='console', num_process=0, par_type='process', single=False, auto_delayed_regex=False, force_verbosity=False, failure_verbosity=0, pdb=False): """ @param reporter: (str) one of provided reporters or ... (class) user defined reporter class (can only be specified from DOIT_CONFIG - never from command line) (reporter instance) - only used in unittests """ # configure PythonAction PythonAction.pm_pdb = pdb # get tasks to be executed # self.control is saved on instance to be used by 'auto' command self.control = TaskControl(self.task_list, auto_delayed_regex=auto_delayed_regex) self.control.process(self.sel_tasks) if single: self.control.process(self.sel_tasks) for task_name in self.control.selected_tasks: task = self.control.tasks[task_name] if task.has_subtask: for task_name in task.task_dep: sub_task = self.control.tasks[task_name] sub_task.task_dep = [] else: task.task_dep = [] # reporter if isinstance(reporter, str): reporter_cls = self.reporters[reporter] else: # user defined class reporter_cls = reporter # outstream if isinstance(outfile, str): outstream = codecs.open(outfile, 'w', encoding='utf-8') else: # outfile is a file-like object (like StringIO or sys.stdout) outstream = outfile self.outstream = outstream # run try: if isinstance(reporter_cls, type): reporter_obj = reporter_cls( outstream, {'failure_verbosity': failure_verbosity}) else: # also accepts reporter instances reporter_obj = reporter_cls stream = Stream(verbosity, force_verbosity) run_args = [self.dep_manager, reporter_obj, continue_, always, stream] if num_process == 0: RunnerClass = Runner else: if par_type == 'process': RunnerClass = MRunner if not MRunner.available(): RunnerClass = MThreadRunner sys.stderr.write( "WARNING: multiprocessing module not available, " "running in parallel using threads.") elif par_type == 'thread': RunnerClass = MThreadRunner else: msg = "Invalid parallel type %s" raise InvalidCommand(msg % par_type) run_args.append(num_process) runner = RunnerClass(*run_args) return runner.run_all(self.control.task_dispatcher()) finally: if isinstance(outfile, str): outstream.close() doit-0.36.0/doit/cmd_strace.py000066400000000000000000000114341423054503100161540ustar00rootroot00000000000000import sys import os import re from .exceptions import InvalidCommand from .action import CmdAction from .task import Task from .cmd_run import Run # filter to display only files from cwd opt_show_all = { 'name': 'show_all', 'short': 'a', 'long': 'all', 'type': bool, 'default': False, 'help': "display all files (not only from within CWD path)", } opt_keep_trace = { 'name': 'keep_trace', 'long': 'keep', 'type': bool, 'default': False, 'help': "save strace command output into strace.txt", } class Strace(Run): doc_purpose = "use strace to list file_deps and targets" doc_usage = "TASK" doc_description = """ The output is a list of files prefixed with 'R' for open in read mode or 'W' for open in write mode. The files are listed in chronological order. This is a debugging feature with many limitations. * can strace only one task at a time * can only strace CmdAction * the process being traced itself might have some kind of cache, that means it might not write a target file if it exist * does not handle chdir So this is NOT 100% reliable, use with care! """ cmd_options = (opt_show_all, opt_keep_trace) TRACE_CMD = "strace -f -e trace=file %s 2>>%s " TRACE_OUT = 'strace.txt' def execute(self, params, args): """remove existing output file if any and do sanity checking""" if os.path.exists(self.TRACE_OUT): # pragma: no cover os.unlink(self.TRACE_OUT) if len(args) != 1: msg = ('strace failed, must select *one* task to strace.' '\nCheck `{} help strace`.'.format(self.bin_name)) raise InvalidCommand(msg) result = Run.execute(self, params, args) if (not params['keep_trace']) and os.path.exists(self.TRACE_OUT): os.unlink(self.TRACE_OUT) return result def _execute(self, show_all): """1) wrap the original action with strace and save output in file 2) add a second task that will generate the report from temp file """ # find task to trace and wrap it selected = self.sel_tasks[0] for task in self.task_list: if task.name == selected: self.wrap_strace(task) break # add task to print report report_strace = Task( 'strace_report', actions=[(find_deps, [self.outstream, self.TRACE_OUT, show_all])], verbosity=2, task_dep=[selected], uptodate=[False], ) self.task_list.append(report_strace) self.sel_tasks.append(report_strace.name) # clear strace file return Run._execute(self, sys.stdout) @classmethod def wrap_strace(cls, task): """wrap task actions into strace command""" wrapped_actions = [] for action in task.actions: if isinstance(action, CmdAction): cmd = cls.TRACE_CMD % (action._action, cls.TRACE_OUT) wrapped = CmdAction(cmd, task, save_out=action.save_out) wrapped_actions.append(wrapped) else: wrapped_actions.append(action) task._action_instances = wrapped_actions # task should be always executed task._extend_uptodate([False]) def find_deps(outstream, strace_out, show_all): """read file witn strace output, return dict with deps, targets""" # 7978 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 # get "mode" file was open, until ')' is closed # ignore rest of line # .*\( # ignore text until '(' # [^"]*" # ignore text until '"' # (?P[^"]*)" # get "file" name inside " # , (\[.*\])* # ignore elements if inside [] - used by execve # (?P[^)]*)\) # get mode opening file # = ].* # check syscall was successful""", regex = re.compile( r'.*\([^"]*"(?P[^"]*)",' + r' (\[.*\])*(?P[^)]*)\) = [^-].*') read = set() write = set() cwd = os.getcwd() if not os.path.exists(strace_out): return with open(strace_out) as text: for line in text: # ignore non file operation match = regex.match(line) if not match: continue rel_name = match.group('file') name = os.path.abspath(rel_name) # ignore files out of cwd if not show_all: if not name.startswith(cwd): continue if 'WR' in match.group('mode'): if name not in write: write.add(name) outstream.write("W %s\n" % name) else: if name not in read: read.add(name) outstream.write("R %s\n" % name) doit-0.36.0/doit/cmdparse.py000066400000000000000000000313571423054503100156540ustar00rootroot00000000000000"""Parse command line options and execute it. Built on top of getopt. optparse can't handle sub-commands. """ import os import getopt import copy from collections import OrderedDict class DefaultUpdate(dict): """A dictionary that has an "update_defaults" method where only items with default values are updated. This is used when you have a dict that has multiple source of values (i.e. hardcoded, config file, command line). And values are updated beginning from the source with higher priority. A default value is added with the method set_default or add_defaults. """ def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) # set of keys that have a non-default value self._non_default_keys = set() def set_default(self, key, value): """set default value for given key""" dict.__setitem__(self, key, value) def add_defaults(self, source): """add default values from another dict @param source: (dict)""" for key, value in source.items(): if key not in self: self.set_default(key, value) def update_defaults(self, update_dict): """like dict.update but do not update items that have a non-default value""" for key, value in update_dict.items(): if key in self._non_default_keys: continue self.set_default(key, value) def __setitem__(self, key, value): """overwrite to keep track of _non_default_keys""" try: self._non_default_keys.add(key) # http://bugs.python.org/issue826897 except AttributeError: self._non_default_keys = set() self._non_default_keys.add(key) dict.__setitem__(self, key, value) class CmdParseError(Exception): """Error parsing options """ class CmdOption(object): """a command line option - name (string) : variable name - section (string): meta info used to group entries when generating help - default (value from its type): default value - type (type): type of the variable. must be able to be initialized taking a single string parameter. if type is bool. option is just a flag. and if present its value is set to True. - short (string): argument short name - long (string): argument long name - inverse (string): argument long name to be the inverse of the default value (only used by boolean options) - choices(list - 2-tuple str): sequence of 2-tuple of choice name, choice description. - help (string): option description """ def __init__(self, opt_dict): # options must contain 'name' and 'default' value opt_dict = opt_dict.copy() for field in ('name', 'default',): if field not in opt_dict: msg = "CmdOption dict %r missing required property '%s'" raise CmdParseError(msg % (opt_dict, field)) self.name = opt_dict.pop('name') self.section = opt_dict.pop('section', '') self.type = opt_dict.pop('type', str) self.set_default(opt_dict.pop('default')) self.short = opt_dict.pop('short', '') self.long = opt_dict.pop('long', '') self.inverse = opt_dict.pop('inverse', '') self.choices = dict(opt_dict.pop('choices', [])) self.help = opt_dict.pop('help', '') self.metavar = opt_dict.pop('metavar', 'ARG') self.env_var = opt_dict.pop('env_var', None) # TODO add some hint for tab-completion scripts # options can not contain any unrecognized field if opt_dict: msg = "CmdOption dict contains invalid property '%s'" raise CmdParseError(msg % list(opt_dict.keys())) def __repr__(self): tmpl = ("{0}({{'name':{1.name!r}, " "'short':{1.short!r}," "'long':{1.long!r} }})") return tmpl.format(self.__class__.__name__, self) def set_default(self, val): """set default value, value is already the expected type""" if self.type is list: self.default = copy.copy(val) else: self.default = val def validate_choice(self, given_value): """raise error is value is not a valid choice""" if given_value not in self.choices: msg = ("Error parsing parameter '{}'. " "Provided '{}' but available choices are: {}.") choices = ", ".join(f"'{k}'" for k in self.choices.keys()) raise CmdParseError(msg.format(self.name, given_value, choices)) _boolean_states = { '1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False, } def str2boolean(self, str_val): """convert string to boolean""" try: return self._boolean_states[str_val.lower()] except Exception: raise ValueError('Not a boolean: {}'.format(str_val)) def str2type(self, str_val): """convert string value to option type value""" try: # no conversion if value is not a string if not isinstance(str_val, str): val = str_val elif self.type is bool: val = self.str2boolean(str_val) elif self.type is list: parts = [p.strip() for p in str_val.split(',')] val = [p for p in parts if p] # remove empty strings else: val = self.type(str_val) except ValueError as exception: msg = (f"Error parsing parameter '{self.name}' {self.type}.\n" f"{exception}\n") raise CmdParseError(msg) if self.choices: self.validate_choice(val) return val @staticmethod def _print_2_columns(col1, col2): """print using a 2-columns format """ column1_len = 24 column2_start = 28 left = (col1).ljust(column1_len) right = col2.replace('\n', '\n' + column2_start * ' ') return " %s %s" % (left, right) def help_param(self): """return string of option's short and long name i.e.: -f ARG, --file=ARG """ opts_str = [] if self.short: if self.type is bool: opts_str.append('-%s' % self.short) else: opts_str.append('-%s %s' % (self.short, self.metavar)) if self.long: if self.type is bool: opts_str.append('--%s' % self.long) else: opts_str.append('--%s=%s' % (self.long, self.metavar)) return ', '.join(opts_str) def help_choices(self): """return string with help for option choices""" if not self.choices: return '' # if choice has a description display one choice per line... if any(self.choices.values()): items = [] for choice in sorted(self.choices): items.append("\n{}: {}".format(choice, self.choices[choice])) return "\nchoices:" + "".join(items) # ... otherwise display in a single line else: return "\nchoices: " + ", ".join(sorted(self.choices.keys())) def help_doc(self): """return list of string of option's help doc Note this is used only to display help on tasks. For commands a better and more complete version is used. see cmd_base:Command.help """ # ignore option that cant be modified on cmd line if not (self.short or self.long): return [] text = [] opt_str = self.help_param() # TODO It should always display option's default value opt_help = self.help % {'default': self.default} opt_choices = self.help_choices() opt_config = 'config: {}'.format(self.name) opt_env = ', environ: {}'.format(self.env_var) if self.env_var else '' desc = f'{opt_help} {opt_choices} ({opt_config}{opt_env})' text.append(self._print_2_columns(opt_str, desc)) # print bool inverse option if self.inverse: opt_str = '--%s' % self.inverse opt_help = 'opposite of --%s' % self.long text.append(self._print_2_columns(opt_str, opt_help)) return text class CmdParse(object): """Process string with command options @ivar options: (list - CmdOption) """ _type = "Command" def __init__(self, options): self._options = OrderedDict((o.name, o) for o in options) def __contains__(self, key): return key in self._options def __getitem__(self, key): return self._options[key] @property def options(self): """return list of options for backward compatibility""" return list(self._options.values()) def get_short(self): """return string with short options for getopt""" short_list = "" for opt in self._options.values(): if not opt.short: continue short_list += opt.short # ':' means option takes a value if opt.type is not bool: short_list += ':' return short_list def get_long(self): """return list with long options for getopt""" long_list = [] for opt in self._options.values(): long_name = opt.long if not long_name: continue # '=' means option takes a value if opt.type is not bool: long_name += '=' long_list.append(long_name) if opt.inverse: long_list.append(opt.inverse) return long_list def get_option(self, opt_str): """return tuple - CmdOption from matching opt_str. or None - (bool) matched inverse """ for opt in self._options.values(): if opt_str in ('-' + opt.short, '--' + opt.long): return opt, False if opt_str == '--' + opt.inverse: return opt, True return None, None def overwrite_defaults(self, new_defaults): """overwrite self.options default values This values typically come from an INI file """ for key, val in new_defaults.items(): if key in self._options: opt = self._options[key] opt.set_default(opt.str2type(val)) def parse_only(self, in_args, params=None): """parse arguments into options(params) and positional arguments @param in_args (list - string): typically sys.argv[1:] @return params, args params(dict): params contain the actual values from the options. where the key is the name of the option. pos_args (list - string): positional arguments """ params = params if params else {} # parse cmdline options using getopt try: opts, args = getopt.getopt(in_args, self.get_short(), self.get_long()) except Exception as error: msg = (f"Error parsing {self._type}: {error} " f"(parsing options: {self.options}). Got: {in_args}") raise CmdParseError(msg) # update params with values from command line for opt, val in opts: this, inverse = self.get_option(opt) if this.type is bool: params[this.name] = not inverse elif this.type is list: params[this.name].append(val) else: params[this.name] = this.str2type(val) return params, args def parse(self, in_args): """parse arguments into options(params) and positional arguments Also get values from shell ENV. Returned params is a `DefaultUpdate` type and includes an item for every option. @param in_args (list - string): typically sys.argv[1:] @return params, args params(dict): params contain the actual values from the options. where the key is the name of the option. pos_args (list - string): positional arguments """ params = DefaultUpdate() # add default values for opt in self._options.values(): params.set_default(opt.name, opt.default) # get values from shell ENV for opt in self._options.values(): if opt.env_var: val = os.getenv(opt.env_var) if val is not None: params[opt.name] = opt.str2type(val) return self.parse_only(in_args, params) class TaskParse(CmdParse): """Process string with command options (for tasks)""" _type = "Task" doit-0.36.0/doit/control.py000066400000000000000000000605041423054503100155320ustar00rootroot00000000000000"""Control tasks execution order""" import fnmatch from collections import deque from collections import OrderedDict import re from .exceptions import InvalidTask, InvalidCommand, InvalidDodoFile from .task import Task, DelayedLoaded from .loader import generate_tasks class RegexGroup(object): '''Helper to keep track of all delayed-tasks which regexp target matches the target specified from command line. ''' def __init__(self, target, tasks): # target name specified in command line self.target = target # set of delayed-tasks names (string) self.tasks = tasks # keep track if the target was already found self.found = False class TaskControl(object): """Manages tasks inter-relationship There are 3 phases 1) the constructor gets a list of tasks and do initialization 2) 'process' the command line options for tasks are processed 3) 'task_dispatcher' dispatch tasks to runner Process dependencies and targets to find out the order tasks should be executed. Also apply filter to exclude tasks from execution. And parse task cmd line options. @ivar tasks: (dict) Key: task name ([taskgen.]name) Value: L{Task} instance @ivar targets: (dict) Key: fileName Value: task_name """ def __init__(self, task_list, auto_delayed_regex=False): self.tasks = OrderedDict() self.targets = {} self.auto_delayed_regex = auto_delayed_regex # name of task in order to be executed # this the order as in the dodo file. the real execution # order might be different if the dependencies require so. self._def_order = [] # list of tasks selected to be executed self.selected_tasks = None # sanity check and create tasks dict for task in task_list: # task must be a Task if not isinstance(task, Task): msg = "Task must be an instance of Task class. %s" raise InvalidTask(msg % (task.__class__)) # task name must be unique if task.name in self.tasks: msg = "Task names must be unique. %s" raise InvalidDodoFile(msg % task.name) self.tasks[task.name] = task self._def_order.append(task.name) # expand wild-card task-dependencies for task in self.tasks.values(): for pattern in task.wild_dep: task.task_dep.extend(self._get_wild_tasks(pattern)) self._check_dep_names() self.set_implicit_deps(self.targets, task_list) def _check_dep_names(self): """check if user input task_dep or setup_task that doesnt exist""" # check task-dependencies exist. for task in self.tasks.values(): for dep in task.task_dep: if dep not in self.tasks: msg = f"{task.name}. Task dependency '{dep}' does not exist." raise InvalidTask(msg) for setup_task in task.setup_tasks: if setup_task not in self.tasks: msg = f"Task '{task.name}': invalid setup task '{setup_task}'." raise InvalidTask(msg) @staticmethod def set_implicit_deps(targets, task_list): """set/add task_dep based on file_dep on a target from another task @param targets: (dict) fileName -> task_name @param task_list: (list - Task) task with newly added file_dep """ # 1) create a dictionary associating every target->task. where the task # builds that target. for task in task_list: for target in task.targets: if target in targets: msg = ("Two different tasks can't have a common target." "'%s' is a target for %s and %s.") raise InvalidTask(msg % (target, task.name, targets[target])) targets[target] = task.name # 2) now go through all dependencies and check if they are target from # another task. # Note: When used with delayed tasks. # It does NOT check if a delayed-task's target is a file_dep # from another previously created task. for task in task_list: TaskControl.add_implicit_task_dep(targets, task, task.file_dep) @staticmethod def add_implicit_task_dep(targets, task, deps_list): """add implicit task_dep for `task` for newly added `file_dep` @param targets: (dict) fileName -> task_name @param task: (Task) task with newly added file_dep @param dep_list: (list - str): list of file_dep for task """ for dep in deps_list: if (dep in targets and targets[dep] not in task.task_dep): task.task_dep.append(targets[dep]) def _get_wild_tasks(self, pattern): """get list of tasks that match pattern""" wild_list = [] for t_name in self._def_order: if fnmatch.fnmatch(t_name, pattern): wild_list.append(t_name) return wild_list def _process_filter(self, task_selection): """process cmd line task options [task_name [-task_opt [opt_value]] ...] ... @param task_selection: list of strings with task names/params or target @return list of task names. Expanding glob and removed params """ filter_list = [] def add_filtered_task(seq, f_name): """add task to list `filter_list` and set task.options from params @return list - str: of elements not yet """ filter_list.append(f_name) # only tasks specified by name can contain parameters if f_name in self.tasks: # parse task_selection the_task = self.tasks[f_name] # Initialize options for the task seq = the_task.init_options(seq) # if task takes positional parameters set all as pos_arg_val if the_task.pos_arg is not None: # cehck value is not set yet # it could be set directly with api.run_tasks() # -> NamespaceTaskLoader.load_tasks() if the_task.pos_arg_val is None: the_task.pos_arg_val = seq seq = [] return seq # process... seq = task_selection[:] # process cmd_opts until nothing left while seq: f_name = seq.pop(0) # always start with a task/target name # select tasks by task-name pattern if '*' in f_name: for task_name in self._get_wild_tasks(f_name): add_filtered_task((), task_name) else: seq = add_filtered_task(seq, f_name) return filter_list def _filter_tasks(self, task_selection): """Select tasks specified by filter. @param task_selection: list of strings with task names/params or target @return (list) of string. where elements are task name. """ selected_task = [] filter_list = self._process_filter(task_selection) for filter_ in filter_list: # by task name if filter_ in self.tasks: selected_task.append(filter_) continue # by target if filter_ in self.targets: selected_task.append(self.targets[filter_]) continue # if can not find name check if it is a sub-task of a delayed basename = filter_.split(':', 1)[0] if basename in self.tasks: loader = self.tasks[basename].loader if not loader: raise InvalidCommand(not_found=filter_) loader.basename = basename self.tasks[filter_] = Task(filter_, None, loader=loader) selected_task.append(filter_) continue # check if target matches any regex delayed_matched = [] # list of Task for task in list(self.tasks.values()): if not task.loader: continue if task.name.startswith('_regex_target'): continue if task.loader.target_regex: if re.match(task.loader.target_regex, filter_): delayed_matched.append(task) elif self.auto_delayed_regex: delayed_matched.append(task) delayed_matched_names = [t.name for t in delayed_matched] regex_group = RegexGroup(filter_, set(delayed_matched_names)) # create extra tasks to load delayed tasks matched by regex for task in delayed_matched: loader = task.loader loader.basename = task.name name = '{}_{}:{}'.format('_regex_target', filter_, task.name) loader.regex_groups[name] = regex_group self.tasks[name] = Task(name, None, loader=loader, file_dep=[filter_]) selected_task.append(name) if not delayed_matched: # not found raise InvalidCommand(not_found=filter_) return selected_task def process(self, task_selection): """ @param task_selection: list of strings with task names/params @return (list - string) each element is the name of a task """ # execute only tasks in the filter in the order specified by filter if task_selection is not None: self.selected_tasks = self._filter_tasks(task_selection) else: # if no filter is defined execute all tasks # in the order they were defined. self.selected_tasks = self._def_order def task_dispatcher(self): """return a TaskDispatcher generator """ assert self.selected_tasks is not None, \ "must call 'process' before this" return TaskDispatcher(self.tasks, self.targets, self.selected_tasks) class ExecNode(object): """Each task will have an instance of this. This used to keep track of waiting events and the generator for dep nodes @ivar run_status (str): contains the result of Dependency.get_status().status modified by runner, value can be: - None: not processed yet - run: task is selected to be executed (it might be running or waiting for setup) - ignore: task wont be executed (user forced deselect) - up-to-date: task wont be executed (no need) - done: task finished its execution """ def __init__(self, task, parent): self.task = task # list of dependencies not processed by _add_task yet self.task_dep = task.task_dep[:] self.calc_dep = task.calc_dep.copy() # ancestors are used to detect cyclic references. # it does not contain a list of tasks that depends on this node # for that check the attribute waiting_me self.ancestors = [] if parent: self.ancestors.extend(parent.ancestors) self.ancestors.append(task.name) # Wait for a task to be selected to its execution # checking if it is up-to-date self.wait_select = False # Wait for a task to finish its execution self.wait_run = set() # task names self.wait_run_calc = set() # task names self.waiting_me = set() # ExecNode self.run_status = None # all ancestors that failed self.bad_deps = [] self.ignored_deps = [] # generator from TaskDispatcher._add_task self.generator = None def reset_task(self, task, generator): """reset task & generator after task is created by its own `loader`""" self.task = task self.task_dep = task.task_dep[:] self.calc_dep = task.calc_dep.copy() self.generator = generator def parent_status(self, parent_node): if parent_node.run_status == 'failure': self.bad_deps.append(parent_node) elif parent_node.run_status == 'ignore': self.ignored_deps.append(parent_node) def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.task.name) def step(self): """get node's next step""" try: return next(self.generator) except StopIteration: return None def no_none(decorated): """decorator for a generator to discard/filter-out None values""" def _func(*args, **kwargs): """wrap generator""" for value in decorated(*args, **kwargs): if value is not None: yield value return _func class TaskDispatcher(object): """Dispatch another task to be selected/executed, mostly handle with MP Note that a dispatched task might not be ready to be executed. """ def __init__(self, tasks, targets, selected_tasks): self.tasks = tasks self.targets = targets self.selected_tasks = selected_tasks self.nodes = {} # key task-name, value: ExecNode # queues self.waiting = set() # of ExecNode self.ready = deque() # of ExecNode self.generator = self._dispatcher_generator(selected_tasks) def _gen_node(self, parent, task_name): """return ExecNode for task_name if not created yet""" node = self.nodes.get(task_name, None) # first time, create node if node is None: node = ExecNode(self.tasks[task_name], parent) node.generator = self._add_task(node) self.nodes[task_name] = node return node # detect cyclic/recursive dependencies if parent and task_name in parent.ancestors: msg = "Cyclic/recursive dependencies for task %s: [%s]" cycle = " -> ".join(parent.ancestors + [task_name]) raise InvalidDodoFile(msg % (task_name, cycle)) def _node_add_wait_run(self, node, task_list, calc=False): """updates node.wait_run @param node (ExecNode) @param task_list (list - str) tasks that node should wait for @param calc (bool) task_list is for calc_dep """ # wait_for: contains tasks that `node` needs to wait for and # were not executed yet. wait_for = set() for name in task_list: dep_node = self.nodes[name] if (not dep_node) or dep_node.run_status in (None, 'run'): wait_for.add(name) else: # if dep task was already executed: # a) set parent status node.parent_status(dep_node) # b) update dependencies from calc_dep results if calc: self._process_calc_dep_results(dep_node, node) # update ExecNode setting parent/dependent relationship for name in wait_for: self.nodes[name].waiting_me.add(node) if calc: node.wait_run_calc.update(wait_for) else: node.wait_run.update(wait_for) @no_none def _add_task(self, node): """@return a generator that produces: - ExecNode for task dependencies - 'wait' to wait for an event (i.e. a dep task run) - Task when ready to be dispatched to runner (run or be selected) - None values are of no interest and are filtered out by the decorator no_none note that after a 'wait' is sent it is the responsibility of the caller to ensure the current ExecNode cleared all its waiting before calling `next()` again on this generator """ this_task = node.task # skip this task if task belongs to a regex_group that already # executed the task used to build the given target if this_task.loader: regex_group = this_task.loader.regex_groups.get(this_task.name, None) if regex_group and regex_group.found: return # add calc_dep & task_dep until all processed # calc_dep may add more deps so need to loop until nothing left while True: calc_dep_list = list(node.calc_dep) node.calc_dep.clear() task_dep_list = node.task_dep[:] node.task_dep = [] for calc_dep in calc_dep_list: yield self._gen_node(node, calc_dep) self._node_add_wait_run(node, calc_dep_list, calc=True) # add task_dep for task_dep in task_dep_list: yield self._gen_node(node, task_dep) self._node_add_wait_run(node, task_dep_list) # do not wait until all possible task_dep are created if (node.calc_dep or node.task_dep): continue # pragma: no cover # coverage cant catch this #198 elif (node.wait_run or node.wait_run_calc): yield 'wait' else: break # generate tasks from a DelayedLoader if this_task.loader: ref = this_task.loader.creator to_load = this_task.loader.basename or this_task.name this_loader = self.tasks[to_load].loader if this_loader and not this_loader.created: task_gen = ref(**this_loader.kwargs) if this_loader.kwargs else ref() new_tasks = generate_tasks(to_load, task_gen, ref.__doc__) TaskControl.set_implicit_deps(self.targets, new_tasks) for nt in new_tasks: if not nt.loader: nt.loader = DelayedLoaded self.tasks[nt.name] = nt # check itself for implicit dep (used by regex_target) TaskControl.add_implicit_task_dep( self.targets, this_task, this_task.file_dep) # remove file_dep since generated tasks are not required # to really create the target (support multiple matches) if regex_group: this_task.file_dep = {} if regex_group.target in self.targets: regex_group.found = True else: regex_group.tasks.remove(this_task.loader.basename) if len(regex_group.tasks) == 0: # In case no task is left, we cannot find a task # generating this target. Print an error message! raise InvalidCommand(not_found=regex_group.target) # mark this loader to not be executed again this_task.loader.created = True this_task.loader = DelayedLoaded # this task was placeholder to execute the loader # now it needs to be re-processed with the real task yield "reset generator" assert False, "This generator can not be used again" # add itself yield this_task # tasks that contain setup-tasks need to be yielded twice if this_task.setup_tasks: # run_status None means task is waiting for other tasks # in order to check if up-to-date. so it needs to wait # before scheduling its setup-tasks. if node.run_status is None: node.wait_select = True yield "wait" # if this task should run, so schedule setup-tasks before itself if node.run_status == 'run': for setup_task in this_task.setup_tasks: yield self._gen_node(node, setup_task) self._node_add_wait_run(node, this_task.setup_tasks) if node.wait_run: yield 'wait' # re-send this task after setup_tasks are sent yield this_task def _get_next_node(self, ready, tasks_to_run): """get ExecNode from (in order): .1 ready .2 tasks_to_run (list in reverse order) """ if ready: return ready.popleft() # get task group from tasks_to_run while tasks_to_run: task_name = tasks_to_run.pop() node = self._gen_node(None, task_name) if node: return node def _update_waiting(self, processed): """updates 'ready' and 'waiting' queues after processed @param processed (ExecNode) or None """ # no task processed, just ignore if processed is None: return node = processed # if node was waiting select must only receive select event if node.wait_select: self.ready.append(node) self.waiting.remove(node) node.wait_select = False # status == run means this was not just select completed if node.run_status == 'run': return for waiting_node in node.waiting_me: waiting_node.parent_status(node) # is_ready indicates if node.generator can be invoked again task_name = node.task.name # node wait_run will be ready if there are nothing left to wait if task_name in waiting_node.wait_run: waiting_node.wait_run.remove(task_name) is_ready = not (waiting_node.wait_run or waiting_node.wait_run_calc) # node wait_run_calc else: assert task_name in waiting_node.wait_run_calc waiting_node.wait_run_calc.remove(task_name) # calc_dep might add new deps that can be run without # waiting for the completion of the remaining deps is_ready = True self._process_calc_dep_results(node, waiting_node) # this node can be further processed if is_ready and (waiting_node in self.waiting): self.ready.append(waiting_node) self.waiting.remove(waiting_node) def _process_calc_dep_results(self, node, waiting_node): # refresh this task dependencies with values got from calc_dep values = node.task.values len_task_deps = len(waiting_node.task.task_dep) old_calc_dep = waiting_node.task.calc_dep.copy() waiting_node.task.update_deps(values) TaskControl.add_implicit_task_dep( self.targets, waiting_node.task, values.get('file_dep', [])) # update node's list of non-processed dependencies new_task_dep = waiting_node.task.task_dep[len_task_deps:] waiting_node.task_dep.extend(new_task_dep) new_calc_dep = waiting_node.task.calc_dep - old_calc_dep waiting_node.calc_dep.update(new_calc_dep) def _dispatcher_generator(self, selected_tasks): """return generator dispatching tasks""" # each selected task will create a tree (from dependencies) of # tasks to be processed tasks_to_run = list(reversed(selected_tasks)) node = None # current active ExecNode while True: # get current node if not node: node = self._get_next_node(self.ready, tasks_to_run) if not node: if self.waiting: # all tasks are waiting, hold on processed = (yield "hold on") self._update_waiting(processed) continue # we are done! return # get next step from current node next_step = node.step() # got None, nothing left for this generator if next_step is None: node = None continue # got a task, send ExecNode to runner if isinstance(next_step, Task): processed = (yield self.nodes[next_step.name]) self._update_waiting(processed) # got new ExecNode, add to ready_queue elif isinstance(next_step, ExecNode): self.ready.append(next_step) # node just performed a delayed creation of tasks, restart elif next_step == "reset generator": node.reset_task(self.tasks[node.task.name], self._add_task(node)) # got 'wait', add ExecNode to waiting queue else: assert next_step == "wait" self.waiting.add(node) node = None doit-0.36.0/doit/dependency.py000066400000000000000000000601021423054503100161620ustar00rootroot00000000000000"""Manage (save/check) task dependency-on-files data.""" import os import hashlib import subprocess import inspect import json from collections import defaultdict from dbm import dumb import dbm as ddbm # uncomment imports below to run tests on all dbm backends... # import dumbdbm as ddbm # import dbm as ddbm # import gdbm as ddbm # note: to check which DBM backend is being used (in py2): # >>> anydbm._defaultmod class DatabaseException(Exception): """Exception class for whatever backend exception""" pass def get_md5(input_data): """return md5 from string or unicode""" byte_data = input_data.encode("utf-8") return hashlib.md5(byte_data).hexdigest() def get_file_md5(path): """Calculate the md5 sum from file content. @param path: (string) file path @return: (string) md5 """ with open(path, 'rb') as file_data: md5 = hashlib.md5() block_size = 128 * md5.block_size while True: data = file_data.read(block_size) if not data: break md5.update(data) return md5.hexdigest() class JSONCodec(): """default implmentation for codec used to save individual task's data""" def __init__(self): self.encoder = json.JSONEncoder() self.decoder = json.JSONDecoder() def encode(self, data): return self.encoder.encode(data) def decode(self, data): return self.decoder.decode(data) class JsonDB(object): """Backend using a single text file with JSON content""" def __init__(self, name, codec): """Open/create a DB file""" self.name = name self.codec = codec if not os.path.exists(self.name): self._db = {} else: self._db = self._load() def _load(self): """load db content from file""" db_file = open(self.name, 'r') try: try: return self.codec.decode(db_file.read()) except ValueError as error: # file contains corrupted json data fname = os.path.abspath(self.name) msg = (f"{error.args[0]}\nInvalid JSON data in {fname}\n" "To fix this problem, you can just remove the " "corrupted file, a new one will be generated.\n") error.args = (msg,) raise DatabaseException(msg) finally: db_file.close() def dump(self): """save DB content in file""" try: db_file = open(self.name, 'w') db_file.write(self.codec.encode(self._db)) finally: db_file.close() def set(self, task_id, dependency, value): """Store value in the DB.""" if task_id not in self._db: self._db[task_id] = {} self._db[task_id][dependency] = value def get(self, task_id, dependency): """Get value stored in the DB. @return: (string) or (None) if entry not found """ if task_id in self._db: return self._db[task_id].get(dependency, None) def in_(self, task_id): """@return bool if task_id is in DB""" return task_id in self._db def remove(self, task_id): """remove saved dependencies from DB for taskId""" if task_id in self._db: del self._db[task_id] def remove_all(self): """remove saved dependencies from DB for all tasks""" self._db = {} class DbmDB(object): """Backend using a DBM file with individual values encoded in JSON On initialization all items are read from DBM file and loaded on ``_dbm``. During execution whenever an item is read (``get`` method) the `json` value is cached on ``_db``. If an item is modified ``_db`` is update and the `id` is added to the `dirty` set. Only on ``dump`` all dirty items values are encoded in json into ``_dbm`` and the DBM file is saved. :ivar str name: file name/path :ivar dbm _dbm: items with json encoded values :ivar dict _db: items with python-dict as value :ivar set dirty: id of modified tasks """ DBM_CONTENT_ERROR_MSG = 'db type could not be determined' def __init__(self, name, codec): """Open/create a DB file""" self.name = name self.codec = codec try: self._dbm = ddbm.open(self.name, 'c') except ddbm.error as exception: message = str(exception) if message == self.DBM_CONTENT_ERROR_MSG: # When a corrupted/old format database is found # suggest the user to just remove the file new_message = ( 'Dependencies file in %(filename)s seems to use ' 'an old format or is corrupted.\n' 'To fix the issue you can just remove the database file(s) ' 'and a new one will be generated.' % {'filename': repr(self.name)}) raise DatabaseException(new_message) else: # Re-raise any other exceptions raise DatabaseException(message) self._db = {} self.dirty = set() def dump(self): """save/close DBM file""" for task_id in self.dirty: self._dbm[task_id] = self.codec.encode(self._db[task_id]) self._dbm.close() def set(self, task_id, dependency, value): """Store value in the DB.""" if task_id not in self._db: self._db[task_id] = {} self._db[task_id][dependency] = value self.dirty.add(task_id) def _in_dbm(self, key): """ should be just:: return key in self._dbm for get()/set() key is convert to bytes but not for 'in' """ return key.encode('utf-8') in self._dbm def get(self, task_id, dependency): """Get value stored in the DB. :return: string or None if entry not found """ # optimization, just try to get it without checking it exists if task_id in self._db: return self._db[task_id].get(dependency, None) else: try: task_data = self._dbm[task_id] except KeyError: return self._db[task_id] = self.codec.decode(task_data.decode('utf-8')) return self._db[task_id].get(dependency, None) def in_(self, task_id): """@return bool if task_id is in DB""" return self._in_dbm(task_id) or task_id in self.dirty def remove(self, task_id): """remove saved dependencies from DB for taskId""" if task_id in self._db: del self._db[task_id] if self._in_dbm(task_id): del self._dbm[task_id] if task_id in self.dirty: self.dirty.remove(task_id) def remove_all(self): """remove saved dependencies from DB for all tasks""" self._db = {} # dumb dbm always opens file in update mode if isinstance(self._dbm, dumb._Database): # pragma: no cover self._dbm._index = {} self._dbm.close() # gdbm can not be running on 2 instances on same thread # see https://bitbucket.org/schettino72/doit/issue/16/ del self._dbm self._dbm = ddbm.open(self.name, 'n') self.dirty = set() class SqliteDB(object): """ sqlite3 json backend """ def __init__(self, name, codec): self.name = name self.codec = codec self._conn = self._sqlite3(self.name) self._cache = {} self._dirty = set() def _sqlite3(self, name): """Open/create a sqlite3 DB file""" # Import sqlite here so it's only imported when required import sqlite3 def dict_factory(cursor, row): """convert row to dict""" data = {} for idx, col in enumerate(cursor.description): data[col[0]] = row[idx] return data def converter(data): return self.codec.decode(data.decode('utf-8')) sqlite3.register_adapter(list, self.codec.encode) sqlite3.register_adapter(dict, self.codec.encode) sqlite3.register_converter("json", converter) conn = sqlite3.connect( name, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES, isolation_level='DEFERRED') conn.row_factory = dict_factory sqlscript = """ create table if not exists doit ( task_id text not null primary key, task_data json );""" try: conn.execute(sqlscript) except sqlite3.DatabaseError as exception: new_message = ( 'Dependencies file in %(filename)s seems to use ' 'an bad format or is corrupted.\n' 'To fix the issue you can just remove the database file(s) ' 'and a new one will be generated.' 'Original error: %(msg)s' % {'filename': repr(name), 'msg': str(exception)}) raise DatabaseException(new_message) return conn def get(self, task_id, dependency): """Get value stored in the DB. @return: (string) or (None) if entry not found """ if task_id in self._cache: return self._cache[task_id].get(dependency, None) else: data = self._cache[task_id] = self._get_task_data(task_id) return data.get(dependency, None) def _get_task_data(self, task_id): data = self._conn.execute('select task_data from doit where task_id=?', (task_id,)).fetchone() return data['task_data'] if data else {} def set(self, task_id, dependency, value): """Store value in the DB.""" if task_id not in self._cache: self._cache[task_id] = {} self._cache[task_id][dependency] = value self._dirty.add(task_id) def in_(self, task_id): if task_id in self._cache: return True if self._conn.execute('select task_id from doit where task_id=?', (task_id,)).fetchone(): return True return False def dump(self): """save/close sqlite3 DB file""" for task_id in self._dirty: self._conn.execute('insert or replace into doit values (?,?)', (task_id, self.codec.encode(self._cache[task_id]))) self._conn.commit() self._conn.close() self._dirty = set() def remove(self, task_id): """remove saved dependencies from DB for taskId""" if task_id in self._cache: del self._cache[task_id] if task_id in self._dirty: self._dirty.remove(task_id) self._conn.execute('delete from doit where task_id=?', (task_id,)) def remove_all(self): """remove saved dependencies from DB for all task""" self._conn.execute('delete from doit') self._cache = {} self._dirty = set() class FileChangedChecker(object): """Base checker for dependencies, must be inherited.""" CheckerError = os.error def exists(self, file_path): return os.path.exists(file_path) def info(self, file_path): return os.stat(file_path) def check_modified(self, file_path, file_stat, state): """Check if file in file_path is modified from previous "state". @param file_path (string): file path @param file_stat: result of os.stat() of file_path @param state: state that was previously saved with ``get_state()`` @returns (bool): True if dep is modified """ raise NotImplementedError() def get_state(self, dep, current_state): """Compute the state of a task after it has been successfully executed. @param dep (str): path of the dependency file. @param current_state (tuple): the current state, saved from a previous execution of the task (None if the task was never run). @returns: the new state. Return None if the state is unchanged. The parameter `current_state` is passed to allow speed optimization, see MD5Checker.get_state(). """ raise NotImplementedError() class MD5Checker(FileChangedChecker): """MD5 checker, uses the md5sum. This is the default checker used by doit. As an optimization the check uses (timestamp, file-size, md5). If the timestamp is the same it considers that the file has the same content. If file size is different its content certainly is modified. Finally the md5 is used for a different timestamp with the same size. """ def check_modified(self, file_path, file_stat, state): """Check if file in file_path is modified from previous "state". """ timestamp, size, file_md5 = state # 1 - if timestamp is not modified file is the same if file_stat.st_mtime == timestamp: return False # 2 - if size is different file is modified if file_stat.st_size != size: return True # 3 - check md5 return file_md5 != get_file_md5(file_path) def get_state(self, dep, current_state): timestamp = os.path.getmtime(dep) # time optimization. if dep is already saved with current # timestamp skip calculating md5 if current_state and current_state[0] == timestamp: return size = os.path.getsize(dep) md5 = get_file_md5(dep) return timestamp, size, md5 class TimestampChecker(FileChangedChecker): """Checker that use only the timestamp.""" def check_modified(self, file_path, file_stat, state): return file_stat.st_mtime != state def get_state(self, dep, current_state): """@returns float: mtime for file `dep`""" return os.path.getmtime(dep) # name of checkers class available CHECKERS = {'md5': MD5Checker, 'timestamp': TimestampChecker} class DependencyStatus(object): """Result object for Dependency.get_status. @ivar status: (str) one of "run", "up-to-date" or "error" """ def __init__(self, get_log): self.get_log = get_log self.status = 'up-to-date' # save reason task is not up-to-date self.reasons = defaultdict(list) self.error_reason = None def add_reason(self, reason, arg, status='run'): """sets state and append reason for not being up-to-date :return boolean: processing should be interrupted """ self.status = status if self.get_log: self.reasons[reason].append(arg) return not self.get_log def set_reason(self, reason, arg): """sets state and reason for not being up-to-date :return boolean: processing should be interrupted """ self.status = 'run' if self.get_log: self.reasons[reason] = arg return not self.get_log def get_error_message(self): '''return str with error message''' return self.error_reason class Dependency(object): """Manage tasks dependencies. Each dependency is saved in "db". There are several "db" backends. It uses a Key-Value format where the key is task-name and value is a dictionary. Each task has a dictionary where keys are `dependency`'s (absolute file path), and the value is the dependency signature. Apart from dependencies other values are also saved on the task dictionary: * ``_values_:`` task's values * ``result:`` task result And also some internal doit attributes: * ``ignore:`` * ``deps:`` * ``checker:`` Those can be accessed with generic DB ``get()``, see below... :ivar string name: filepath of the DB file :ivar bool _closed: DB was flushed to file """ def __init__(self, db_class, backend_name, checker_cls=MD5Checker, codec_cls=JSONCodec): self._closed = False self.checker = checker_cls() self.db_class = db_class self.backend = db_class(backend_name, codec=codec_cls()) self._set = self.backend.set self._get = self.backend.get self.remove = self.backend.remove self.remove_all = self.backend.remove_all self._in = self.backend.in_ self.name = self.backend.name def close(self): """Write DB in file""" if not self._closed: self.backend.dump() self._closed = True ####### task specific def save_success(self, task, result_hash=None): """save info after a task is successfully executed :param str result_hash: explicitly set result_hash """ # save task values self._set(task.name, "_values_:", task.values) # save task result md5 if result_hash is not None: self._set(task.name, "result:", result_hash) elif task.result: if isinstance(task.result, dict): self._set(task.name, "result:", task.result) else: self._set(task.name, "result:", get_md5(task.result)) # file-dep self._set(task.name, 'checker:', self.checker.__class__.__name__) for dep in task.file_dep: state = self.checker.get_state(dep, self._get(task.name, dep)) if state is not None: self._set(task.name, dep, state) # save list of file_deps self._set(task.name, 'deps:', tuple(task.file_dep)) def get_values(self, task_name): """get all saved values from a task :return dict: """ values = self._get(task_name, '_values_:') return values or {} def get_value(self, task_id, key_name): """get saved value from task :param str task_id: :param str key_name: key result dict of the value """ if not self._in(task_id): # FIXME do not use generic exception raise Exception("taskid '%s' has no computed value!" % task_id) values = self.get_values(task_id) if key_name not in values: msg = "Invalid arg name. Task '%s' has no value for '%s'." raise Exception(msg % (task_id, key_name)) return values[key_name] def get_result(self, task_name): """get the result saved from a task :return (dict or md5sum): """ return self._get(task_name, 'result:') def remove_success(self, task): """remove saved info from task""" self.remove(task.name) def ignore(self, task): """mark task to be ignored""" self._set(task.name, 'ignore:', '1') def status_is_ignore(self, task): """check if task is marked to be ignored""" return self._get(task.name, "ignore:") def get_status(self, task, tasks_dict, get_log=False): """Check if task is up to date. set task.dep_changed If the checker class changed since the previous run, the task is deleted, to be sure that its state is not re-used. @param task: (Task) @param tasks_dict: (dict: Task) passed to objects used on uptodate @param get_log: (bool) if True, adds all reasons to the return object why this file will be rebuild. @return: (DependencyStatus) a status object with possible status values up-to-date, run or error task.dep_changed (list-strings): file-dependencies that are not up-to-date if task not up-to-date because of a target, returned value will contain all file-dependencies regardless they are up-to-date or not. """ result = DependencyStatus(get_log) task.dep_changed = [] # check uptodate bool/callables uptodate_result_list = [] for utd, utd_args, utd_kwargs in task.uptodate: # if parameter is a callable if hasattr(utd, '__call__'): # FIXME control verbosity, check error messages # 1) setup object with global info all tasks if isinstance(utd, UptodateCalculator): utd.setup(self, tasks_dict) # 2) add magic positional args for `task` and `values` # if present. spec_args = list(inspect.signature(utd).parameters.keys()) magic_args = [] for i, name in enumerate(spec_args): if i == 0 and name == 'task': magic_args.append(task) elif i == 1 and name == 'values': magic_args.append(self.get_values(task.name)) args = magic_args + utd_args # 3) call it and get result uptodate_result = utd(*args, **utd_kwargs) elif isinstance(utd, str): uptodate_result = subprocess.call( utd, shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0 # parameter is a value else: uptodate_result = utd # None means uptodate was not really calculated and should be # just ignored if uptodate_result is None: continue uptodate_result_list.append(uptodate_result) if not uptodate_result: result.add_reason('uptodate_false', (utd, utd_args, utd_kwargs)) # any uptodate check is false if not get_log and result.status == 'run': return result # no dependencies means it is never up to date. if not (task.file_dep or uptodate_result_list): if result.set_reason('has_no_dependencies', True): return result # if target file is not there, task is not up to date for targ in task.targets: if not self.checker.exists(targ): task.dep_changed = list(task.file_dep) if result.add_reason('missing_target', targ): return result # check for modified file_dep checker previous = self._get(task.name, 'checker:') checker_name = self.checker.__class__.__name__ if previous and previous != checker_name: task.dep_changed = list(task.file_dep) # remove all saved values otherwise they might be re-used by # some optimization on MD5Checker.get_state() self.remove(task.name) if result.set_reason('checker_changed', (previous, checker_name)): return result # check for modified file_dep previous = self._get(task.name, 'deps:') previous_set = set(previous) if previous else None if previous_set and previous_set != task.file_dep: if get_log: added_files = sorted(list(task.file_dep - previous_set)) removed_files = sorted(list(previous_set - task.file_dep)) result.set_reason('added_file_dep', added_files) result.set_reason('removed_file_dep', removed_files) result.status = 'run' # list of file_dep that changed check_modified = self.checker.check_modified changed = [] for dep in task.file_dep: state = self._get(task.name, dep) try: file_stat = self.checker.info(dep) except self.checker.CheckerError: error_msg = "Dependent file '{}' does not exist.".format(dep) result.error_reason = error_msg.format(dep) if result.add_reason('missing_file_dep', dep, 'error'): return result else: if state is None or check_modified(dep, file_stat, state): changed.append(dep) task.dep_changed = changed if len(changed) > 0: result.set_reason('changed_file_dep', changed) return result ############# class UptodateCalculator(object): """Base class for 'uptodate' that need access to all tasks """ def __init__(self): self.get_val = None # Dependency._get self.tasks_dict = None # dict with all tasks def setup(self, dep_manager, tasks_dict): """@param""" self.get_val = dep_manager._get self.tasks_dict = tasks_dict doit-0.36.0/doit/doit_cmd.py000066400000000000000000000247731423054503100156440ustar00rootroot00000000000000"""doit CLI (command line interface)""" import os import sys import traceback from collections import defaultdict from configparser import ConfigParser import importlib from .version import VERSION from .plugin import PluginDict from .exceptions import InvalidDodoFile, InvalidCommand, InvalidTask from .cmdparse import CmdOption, CmdParseError, CmdParse from .cmd_base import get_loader from .cmd_help import Help from .cmd_run import Run from .cmd_clean import Clean from .cmd_list import List from .cmd_info import Info from .cmd_forget import Forget from .cmd_ignore import Ignore from .cmd_dumpdb import DumpDB from .cmd_strace import Strace from .cmd_completion import TabCompletion from .cmd_resetdep import ResetDep # used to save variable values passed from command line _CMDLINE_VARS = None def reset_vars(): global _CMDLINE_VARS _CMDLINE_VARS = {} def get_var(name, default=None): # Ignore if not initialized. # This is a work-around for Windows multi-processing # See https://github.com/pydoit/doit/issues/164 if _CMDLINE_VARS is None: return None return _CMDLINE_VARS.get(name, default) def set_var(name, value): _CMDLINE_VARS[name] = value class DoitConfig(): """Parse and store values taken from INI and TOML configuration files""" # support TOML python libs _TOML_LIBS = ['tomllib', 'tomli', 'tomlkit'] PLUGIN_TYPES = ['command', 'loader', 'backend', 'reporter'] def __init__(self): self._toml = None self.config = defaultdict(dict) def loads(self, config_filenames): for config_filename in config_filenames: if str(config_filename).lower().endswith('.toml'): prefix = 'tool.doit' if config_filename == 'pyproject.toml' else '' toml_config = self.load_config_toml(config_filename, prefix) for section in toml_config: self.config[section].update(toml_config[section].items()) else: # INI config format ini_config = self.load_config_ini(config_filename) for section in ini_config.sections(): self.config[section].update(ini_config[section].items()) @property def toml(self): """get available toml lib, if any""" if self._toml is None: for toml_lib in self._TOML_LIBS: try: self._toml = importlib.import_module(toml_lib) break except ImportError: pass return self._toml def as_dict(self): """return values in legacy dict format""" # FIXME: convert INI to TOML format instead of converting TOML to INI return self.config @staticmethod def load_config_ini(filenames): """read config from INI files :param files: str or list of str. Like ConfigParser.read() param filenames """ cfg_parser = ConfigParser(allow_no_value=True, delimiters=('=',)) cfg_parser.optionxform = str # preserve case of option names cfg_parser.read(filenames) return cfg_parser def load_config_toml(self, filename, prefix): """read config from a TOML file, adapt to ConfigParser structure :param filename: str returns an empty dictionary if tool.doit is not present in the parsed data """ toml_config = {} raw = None if os.path.exists(filename): if not self.toml: sys.stderr.write( f'''WARNING: File "{filename}" might contain doit configuration,''' '''but a TOML parser is not available.\n''' f'''\tPlease install one of: {', '.join(self._TOML_LIBS)}.\n''' ) else: with open(filename, encoding='utf-8') as fp: text = fp.read() raw = self.toml.loads(text) if raw and isinstance(raw, dict): if prefix: for part in prefix.split('.'): raw = raw.get(part, {}) doit_toml = raw # hoist /tool/doit/plugins/AAA to /AAA for plugin_type, plugins in doit_toml.pop('plugins', {}).items(): assert plugin_type in self.PLUGIN_TYPES toml_config[plugin_type.upper()] = plugins # hoist /tool/doit/commands/bbb to /bbb for command, command_config in doit_toml.pop('commands', {}).items(): toml_config[command] = command_config # hoist /tool/doit/tasks/ccc to /task:ccc for task, task_config in doit_toml.pop('tasks', {}).items(): toml_config['task:{}'.format(task)] = task_config # hoist /tool/doit/ddd to /GLOBAL/ddd for global_name, global_value in doit_toml.items(): toml_config.setdefault('GLOBAL', {})[global_name] = global_value return toml_config class DoitMain(object): # core doit commands BIN_NAME = sys.argv[0].split('/')[-1] DOIT_CMDS = (Help, Run, List, Info, Clean, Forget, Ignore, DumpDB, Strace, TabCompletion, ResetDep) def __init__(self, task_loader=None, config_filenames=('pyproject.toml', 'doit.cfg'), extra_config=None): """ :param extra_config: dict of extra argument values (by argument name) This is parameter is only used by explicit API call. """ self.task_loader = task_loader # backward compability: convert single filename to list if isinstance(config_filenames, str): config_filenames = [config_filenames] # ignore config files do that not exist config_filenames = [fn for fn in config_filenames if os.path.exists(fn)] self.config = defaultdict(dict) if extra_config: for section, items in extra_config.items(): self.config[section].update(items) # combine config option from INI/TOML files and API config_in = DoitConfig() config_in.loads(config_filenames) for section, vals in config_in.as_dict().items(): self.config[section].update(vals) @staticmethod def print_version(): """print doit version (includes path location)""" print(".".join([str(i) for i in VERSION])) print("lib @", os.path.dirname(os.path.abspath(__file__))) def get_cmds(self): """get all sub-commands :return dict: name - Command class """ sub_cmds = PluginDict() # core doit commands for cmd_cls in self.DOIT_CMDS: sub_cmds[cmd_cls.get_name()] = cmd_cls # plugin commands sub_cmds.add_plugins(self.config, 'COMMAND') return sub_cmds def process_args(self, cmd_args): """process cmd line set "global" variables/parameters return list of args without processed variables """ # get cmdline variables from args reset_vars() args_no_vars = [] for arg in cmd_args: if (arg[0] != '-') and ('=' in arg): name, value = arg.split('=', 1) set_var(name, value) else: args_no_vars.append(arg) return args_no_vars def get_commands(self): # pragma: no cover '''Notice for application subclassing DoitMain with old API''' msg = ('ERROR: You are using doit version {}, it is too new! ' 'This application requires version <= 0.27.\n') sys.stderr.write(msg.format('.'.join(str(v) for v in VERSION))) sys.exit(3) def run(self, all_args): """entry point for all commands :param all_args: list of string arguments from command line return codes: 0: tasks executed successfully 1: one or more tasks failed 2: error while executing a task 3: error before task execution starts, in this case the Reporter is not used. So be aware if you expect a different formatting (like JSON) from the Reporter. """ # get list of available commands sub_cmds = self.get_cmds() task_loader = get_loader(self.config, self.task_loader, sub_cmds) # special parameters that dont run anything if all_args: if all_args[0] == "--version": self.print_version() return 0 if all_args[0] == "--help": help = Help(task_loader=task_loader, config=self.config, bin_name=self.BIN_NAME, cmds=sub_cmds) help.print_usage(sub_cmds.to_dict()) return 0 # loader command options might appear before command name try: loader_opt_parser = CmdParse( [CmdOption(opt) for opt in task_loader.cmd_options]) loader_params, cmd_args = loader_opt_parser.parse_only(all_args) except CmdParseError: # normal to fail parsing if RUN command is not explicit loader_params = {} cmd_args = all_args # get "global vars" from cmd-line args = self.process_args(cmd_args) # get specified sub-command or use default='run' if len(args) == 0 or args[0] not in sub_cmds: specified_run = False cmd_name = 'run' else: specified_run = True cmd_name = args.pop(0) # execute command command = sub_cmds.get_plugin(cmd_name)( task_loader=task_loader, config=self.config, bin_name=self.BIN_NAME, cmds=sub_cmds, opt_vals=loader_params, ) try: return command.parse_execute(args) # dont show traceback for user errors. except (CmdParseError, InvalidDodoFile, InvalidCommand, InvalidTask) as err: if isinstance(err, InvalidCommand): err.cmd_used = cmd_name if specified_run else None err.bin_name = self.BIN_NAME sys.stderr.write("ERROR: %s\n" % str(err)) return 3 except Exception: if command.pdb: # pragma: no cover import pdb pdb.post_mortem(sys.exc_info()[2]) sys.stderr.write(traceback.format_exc()) return 3 doit-0.36.0/doit/exceptions.py000066400000000000000000000063471423054503100162400ustar00rootroot00000000000000"""Handle exceptions generated from 'user' code""" import sys import traceback class InvalidCommand(Exception): """Invalid command line argument.""" def __init__(self, *args, **kwargs): self.not_found = kwargs.pop('not_found', None) super(InvalidCommand, self).__init__(*args, **kwargs) self.cmd_used = None self.bin_name = 'doit' # default but might be overwriten def __str__(self): if self.not_found is None: return super(InvalidCommand, self).__str__() if self.cmd_used: msg_task_not_found = ( 'command `{cmd_used}` invalid parameter: "{not_found}".' ' Must be a task, or a target.\n' 'Type "{bin_name} list" to see available tasks') return msg_task_not_found.format(**self.__dict__) else: msg_cmd_task_not_found = ( 'Invalid parameter: "{not_found}".' ' Must be a command, task, or a target.\n' 'Type "{bin_name} help" to see available commands.\n' 'Type "{bin_name} list" to see available tasks.\n') return msg_cmd_task_not_found.format(**self.__dict__) class InvalidDodoFile(Exception): """Invalid dodo file""" pass class InvalidTask(Exception): """Invalid task instance. User error on specifying the task.""" pass class CatchedException(): """DEPRECATED, use BaseFail instead. 2022-04-22 0.36.0 release. Wrong grammar and not all BaseFail contains an Exception :ivar report: used by (some) reporters to decide if Failure/Error should be in printed """ def __init__(self, msg, exception=None, report=True): self.message = msg self.traceback = '' self.report = report # It would be nice to include original exception, but they are not always pickable # https://stackoverflow.com/questions/49715881/how-to-pickle-inherited-exceptions if isinstance(exception, BaseFail): self.traceback = exception.traceback elif exception is not None: self.traceback = traceback.format_exception( exception.__class__, exception, sys.exc_info()[2]) def get_msg(self): """return full exception description (includes traceback)""" return "%s\n%s" % (self.message, "".join(self.traceback)) def get_name(self): """get fail kind name""" return self.__class__.__name__ def __repr__(self): return "(<%s> %s)" % (self.get_name(), self.message) def __str__(self): return "%s\n%s" % (self.get_name(), self.get_msg()) class BaseFail(CatchedException): """This used to save info Task failures/errors Might contain a caught Exception. """ pass class TaskFailed(BaseFail): """Task execution was not successful.""" pass class TaskError(BaseFail): """Error while trying to execute task.""" pass class UnmetDependency(TaskError): """Task was not executed because a dependent task failed or is ignored""" pass class SetupError(TaskError): """Error while trying to execute setup object""" pass class DependencyError(TaskError): """Error while trying to check if task is up-to-date or saving task status""" pass doit-0.36.0/doit/globals.py000066400000000000000000000004051423054503100154670ustar00rootroot00000000000000"""Simple registry of singletons.""" class Globals: """Accessors to doit singletons. :cvar dep_manager: (doit.dependency.Dependency) The doit dependency manager, holding all persistent task data. """ dep_manager = None doit-0.36.0/doit/loader.py000066400000000000000000000356641423054503100153310ustar00rootroot00000000000000"""Loads dodo file (a python module) and convert them to 'tasks' """ import os import sys import copy import inspect import importlib from collections import OrderedDict from .exceptions import InvalidTask, InvalidCommand, InvalidDodoFile from .task import DelayedLoader, Task, dict_to_task from .cmdparse import TaskParse, CmdOption # Directory path from where doit was executed. # Set by loader, to be used on dodo.py by users. initial_workdir = None # TASK_STRING: (string) prefix used to identify python function # that are task generators in a dodo file. TASK_STRING = "task_" def flat_generator(gen, gen_doc=''): """return only values from generators if any generator yields another generator it is recursively called """ for item in gen: if inspect.isgenerator(item): item_doc = item.gi_code.co_consts[0] for value, value_doc in flat_generator(item, item_doc): yield value, value_doc else: yield item, gen_doc def get_module(dodo_file, cwd=None, seek_parent=False): """ Find python module defining tasks, it is called "dodo" file. @param dodo_file(str): path to file containing the tasks @param cwd(str): path to be used cwd, if None use path from dodo_file @param seek_parent(bool): search for dodo_file in parent paths if not found @return (module) dodo module """ global initial_workdir initial_workdir = os.getcwd() def exist_or_raise(path): """raise exception if file on given path doesnt exist""" if not os.path.exists(path): msg = (f"Could not find dodo file '{path}'.\n" "Please use '-f' to specify file name.\n") raise InvalidDodoFile(msg) # get absolute path name if os.path.isabs(dodo_file): dodo_path = dodo_file exist_or_raise(dodo_path) else: if not seek_parent: dodo_path = os.path.abspath(dodo_file) exist_or_raise(dodo_path) else: # try to find file in any folder above current_dir = initial_workdir dodo_path = os.path.join(current_dir, dodo_file) file_name = os.path.basename(dodo_path) parent = os.path.dirname(dodo_path) while not os.path.exists(dodo_path): new_parent = os.path.dirname(parent) if new_parent == parent: # reached root path exist_or_raise(dodo_file) parent = new_parent dodo_path = os.path.join(parent, file_name) ## load module dodo file and set environment base_path, file_name = os.path.split(dodo_path) # make sure dodo path is on sys.path so we can import it sys.path.insert(0, base_path) if cwd is None: # by default cwd is same as dodo.py base path full_cwd = base_path else: # insert specified cwd into sys.path full_cwd = os.path.abspath(cwd) if not os.path.isdir(full_cwd): msg = "Specified 'dir' path must be a directory.\nGot '%s'(%s)." raise InvalidCommand(msg % (cwd, full_cwd)) sys.path.insert(0, full_cwd) # file specified on dodo file are relative to cwd os.chdir(full_cwd) # get module containing the tasks return importlib.import_module(os.path.splitext(file_name)[0]) def create_after(executed=None, target_regex=None, creates=None): """Annotate a task-creator function with delayed loader info""" def decorated(func): func.doit_create_after = DelayedLoader( func, executed=executed, target_regex=target_regex, creates=creates ) return func return decorated def task_params(param_def=None): """Annotate a task-creator function with definition of parameters to get arguments from cmd line """ if param_def is None or type(param_def) != list: raise ValueError('task_params must be called with a valid parameter definition.') def decorated(func): func._task_creator_params = param_def return func return decorated def load_tasks(namespace, command_names=(), allow_delayed=False, args=(), config=None, task_opts=None): """Find task-creators and create tasks @param namespace: (dict) containing the task creators, it might contain other stuff @param command_names: (list - str) blacklist for task names @param allow_delayed: (bool) if True ignore doit_crate_after['executed'] @param args: (list - str) command line arguments (task names and option arguments) @param config: (dict) configuration taken from TOML and INI files `load_all == False` is used by the runner to delay the creation of tasks until a dependent task is executed. This is only used by the `run` command, other commands should always load all tasks since it wont execute any task. @return task_list (list) of Tasks in the order they were defined on the file """ funcs = _get_task_creators(namespace, command_names) # sort by the order functions were defined (line number) # TODO: this ordering doesnt make sense when generators come # from different modules funcs.sort(key=lambda obj: obj[2]) task_list = [] def _append_params(tasks, param_def): """Apply parameters defined for the task generator to the tasks defined by the generator. """ for task in tasks: if task.subtask_of is None: # only parent tasks # task.params can not be used with creator_params if task.params: msg = (f"Task '{task.name}'. `params` attribute can not be used" " in conjuction with `@task_params`") raise InvalidTask(msg) task.creator_params = param_def def _process_gen(ref, creator_kwargs): """process a task creator, generating tasks""" gen_tasks = generate_tasks(name, ref(**creator_kwargs), ref.__doc__) if hasattr(ref, '_task_creator_params'): _append_params(gen_tasks, ref._task_creator_params) task_list.extend(gen_tasks) def _add_delayed(tname, ref, original_delayed, kwargs): # Make sure create_after can be used on class methods. # delayed.creator is initially set by the decorator, # so always an unbound function. # Here we re-assign with the reference taken on doit load phase # because it is bounded method. this_delayed = copy.copy(delayed) this_delayed.creator = ref d_task = Task(tname, None, loader=this_delayed, doc=delayed.creator.__doc__) if hasattr(ref, '_task_creator_params'): this_delayed.kwargs = kwargs d_task.creator_params = getattr(ref, '_task_creator_params', None) task_list.append(d_task) # Map arg_name to its position. # Save only args that do not start with `-` (potentially task names) arg_pos = {} for index, term in enumerate(args): if term[0] != '-': arg_pos[term] = index for name, ref, _ in funcs: delayed = getattr(ref, 'doit_create_after', None) # Parse command line arguments for task generator parameters creator_params = getattr(ref, '_task_creator_params', None) if creator_params is not None: parser = TaskParse([CmdOption(opt) for opt in creator_params]) # Add task options from config, if present if config: task_stanza = 'task:' + name if task_stanza in config: parser.overwrite_defaults(config[task_stanza]) # option params passed through API if task_opts and name in task_opts: creator_kwargs = task_opts[name] # if relevant command line defaults are available parse those elif name in arg_pos: creator_kwargs, _ = parser.parse(args[arg_pos[name] + 1:]) else: creator_kwargs, _ = parser.parse('') else: creator_kwargs = {} if not delayed: # not a delayed task, just run creator _process_gen(ref, creator_kwargs) elif delayed.creates: # delayed with explicit task basename for tname in delayed.creates: _add_delayed(tname, ref, delayed, creator_kwargs) elif allow_delayed: # delayed no explicit name, cmd run _add_delayed(name, ref, delayed, creator_kwargs) else: # delayed no explicit name, cmd list (run creator) _process_gen(ref, creator_kwargs) return task_list def _get_task_creators(namespace, command_names): """get functions defined in the `namespace` and select the task-creators A task-creator is a function that: - name starts with the string TASK_STRING - has the attribute `create_doit_tasks` @return (list - func) task-creators """ funcs = [] prefix_len = len(TASK_STRING) # get all functions that are task-creators for name, ref in namespace.items(): # Do not solicit tasks from the @task_params decorator. if ref is task_params: continue # function is a task creator because of its name if name.startswith(TASK_STRING) and ( inspect.isfunction(ref) or inspect.ismethod(ref)): # remove TASK_STRING prefix from name task_name = name[prefix_len:] # object is a task creator because it contains the special method elif hasattr(ref, 'create_doit_tasks'): ref = ref.create_doit_tasks # create_doit_tasks might have a basename to overwrite task name. task_name = getattr(ref, 'basename', name) # ignore functions that are not a task creator else: # pragma: no cover # coverage can't get "else: continue" continue # tasks can't have the same name of a commands if task_name in command_names: msg = (f"Task can't be called '{task_name}' because this is a command name." " Please choose another name.") raise InvalidDodoFile(msg) # get line number where function is defined line = inspect.getsourcelines(ref)[1] # add to list task generator functions funcs.append((task_name, ref, line)) return funcs def load_doit_config(dodo_module): """ @param dodo_module (dict) dict with module members """ doit_config = dodo_module.get('DOIT_CONFIG', {}) if not isinstance(doit_config, dict): msg = ("DOIT_CONFIG must be a dict. got:'%s'%s") raise InvalidDodoFile(msg % (repr(doit_config), type(doit_config))) return doit_config def _generate_task_from_return(func_name, task_dict, gen_doc): """generate a single task from a dict returned by a task generator""" if 'name' in task_dict: raise InvalidTask("Task '%s'. Only subtasks use field name." % func_name) task_dict['name'] = task_dict.pop('basename', func_name) # Use task generator docstring # if no doc present in task dict if 'doc' not in task_dict: task_dict['doc'] = gen_doc return dict_to_task(task_dict) def _generate_task_from_yield(tasks, func_name, task_dict, gen_doc): """generate a single task from a dict yielded by task generator @param tasks: dictionary with created tasks @return None: the created task is added to 'tasks' dict """ # check valid input if not isinstance(task_dict, dict): raise InvalidTask("Task '%s' must yield dictionaries" % func_name) msg_dup = "Task generation '%s' has duplicated definition of '%s'" basename = task_dict.pop('basename', None) # if has 'name' this is a sub-task if 'name' in task_dict: basename = basename or func_name # if subname is None attributes from group task if task_dict['name'] is None: task_dict['name'] = basename task_dict['actions'] = None group_task = dict_to_task(task_dict) group_task.has_subtask = True tasks[basename] = group_task return # name is '.' full_name = f"{basename}:{task_dict['name']}" if full_name in tasks: raise InvalidTask(msg_dup % (func_name, full_name)) task_dict['name'] = full_name sub_task = dict_to_task(task_dict) sub_task.subtask_of = basename # get/create task group group_task = tasks.get(basename) if group_task: if not group_task.has_subtask: raise InvalidTask(msg_dup % (func_name, basename)) else: group_task = Task(basename, None, doc=gen_doc, has_subtask=True) tasks[basename] = group_task group_task.task_dep.append(sub_task.name) tasks[sub_task.name] = sub_task # NOT a sub-task else: if not basename: raise InvalidTask( "Task '%s' must contain field 'name' or 'basename'. %s" % (func_name, task_dict)) if basename in tasks: raise InvalidTask(msg_dup % (func_name, basename)) task_dict['name'] = basename # Use task generator docstring if no doc present in task dict if 'doc' not in task_dict: task_dict['doc'] = gen_doc tasks[basename] = dict_to_task(task_dict) def generate_tasks(func_name, gen_result, gen_doc=None): """Create tasks from a task generator result. @param func_name: (string) name of taskgen function @param gen_result: value returned by a task generator function it can be a dict or generator (generating dicts) @param gen_doc: (string/None) docstring from the task generator function @param param_def: (dict) additional task parameter definitions passed down from generator @return: (list - Task) """ # a task instance, just return it without any processing if isinstance(gen_result, Task): return (gen_result,) # task described as a dictionary if isinstance(gen_result, dict): return [_generate_task_from_return(func_name, gen_result, gen_doc)] # a generator if inspect.isgenerator(gen_result): tasks = OrderedDict() # task_name: task # the generator return subtasks as dictionaries for task_dict, x_doc in flat_generator(gen_result, gen_doc): if isinstance(task_dict, Task): tasks[task_dict.name] = task_dict else: _generate_task_from_yield(tasks, func_name, task_dict, x_doc) if tasks: return list(tasks.values()) else: # special case task_generator did not generate any task # create an empty group task return [Task(func_name, None, doc=gen_doc, has_subtask=True)] if gen_result is None: return () raise InvalidTask( "Task '%s'. Must return a dictionary or generator. Got %s" % (func_name, type(gen_result))) doit-0.36.0/doit/plugin.py000066400000000000000000000074341423054503100153530ustar00rootroot00000000000000import sys import importlib def entry_points_impl(): # entry_points is available since 3.8 but "horrible inefficient" if sys.version_info < (3, 10): from importlib_metadata import entry_points else: from importlib.metadata import entry_points return entry_points class PluginEntry(object): """A Plugin entry point The entry-point is not loaded/imported on creation. Use the method `get()` to import the module and get the attribute. """ class Sentinel(object): pass # indicate the entry-point object is not loaded yet NOT_LOADED = Sentinel() def __init__(self, category, name, location): """ :param category str: plugin category name :param name str: plugin name (as used by doit) :param location str: python object location as : """ self.obj = self.NOT_LOADED self.category = category self.name = name self.location = location def __repr__(self): return "PluginEntry('{}', '{}', '{}')".format( self.category, self.name, self.location) def get(self): """return obj, get from cache or load""" if self.obj is self.NOT_LOADED: self.obj = self.load() return self.obj def load(self): """load/import reference to obj from named module/obj""" module_name, obj_name = self.location.split(':') try: module = importlib.import_module(module_name) except ImportError: raise Exception('Plugin {} module `{}` not found.'.format( self.category, module_name)) try: obj = getattr(module, obj_name) except AttributeError: raise Exception('Plugin {}:{} module `{}` has no {}.'.format( self.category, self.name, module_name, obj_name)) return obj class PluginDict(dict): """Item values *might* be a PluginEntry or a direct reference to class/obj. Values should not be accessed directly, use `get_plugin()` to make sure the plugin is loaded. Typically, one dict is created for each kind of plugin. doit supports 4 categories: - COMMAND - LOADER - BACKEND - REPORTER """ entry_point_prefix = 'doit' def add_plugins(self, cfg_data, category): """read all items from a ConfigParser section containing plugins & entry-points. Plugins from entry-point have higher priority """ self.update(self._from_ini(cfg_data, category)) self.update(self._from_entry_points(category)) def _from_ini(self, cfg_data, category): """plugins from INI file INI `section` names map exactly to plugin `category`. """ result = {} if category in cfg_data: for name, location in cfg_data[category].items(): result[name] = PluginEntry(category, name, location) return result def _from_entry_points(self, category): """get all plugins from setuptools entry_points""" result = {} group = f"{self.entry_point_prefix}.{category}" entry_points = entry_points_impl() for point in entry_points(group=group): name = point.name location = "{}:{}".format(point.module, point.attr) result[name] = PluginEntry(category, name, location) return result def get_plugin(self, key): """load and return a single plugin""" val = self[key] if isinstance(val, PluginEntry): val.name = key # overwrite obj name attribute return val.get() else: return val def to_dict(self): """return a standard dict with all plugins values loaded (no PluginEntry)""" return {k: self.get_plugin(k) for k in self.keys()} doit-0.36.0/doit/reporter.py000066400000000000000000000233251423054503100157140ustar00rootroot00000000000000"""Reports doit execution status/results""" import sys import time import datetime import json from io import StringIO from .exceptions import BaseFail class ConsoleReporter(object): """Default reporter. print results on console/terminal (stdout/stderr) @ivar failure_verbosity: (int) include captured stdout/stderr on failure report even if already shown. """ # short description, used by the help system desc = 'console output' def __init__(self, outstream, options): # save non-successful result information (include task errors) self.failures = [] self.runtime_errors = [] self.failure_verbosity = options.get('failure_verbosity', 0) self.outstream = outstream def write(self, text): self.outstream.write(text) def initialize(self, tasks, selected_tasks): """called just after tasks have been loaded before execution starts""" pass def get_status(self, task): """called when task is selected (check if up-to-date)""" pass def execute_task(self, task): """called when execution starts""" # ignore tasks that do not define actions # ignore private/hidden tasks (tasks that start with an underscore) if task.actions and (task.name[0] != '_'): self.write('. %s\n' % task.title()) def add_failure(self, task, fail: BaseFail): """called when execution finishes with a failure""" result = {'task': task, 'exception': fail} if fail.report: self.failures.append(result) self._write_failure(result) def add_success(self, task): """called when execution finishes successfully""" pass def skip_uptodate(self, task): """skipped up-to-date task""" if task.name[0] != '_': self.write("-- %s\n" % task.title()) def skip_ignore(self, task): """skipped ignored task""" self.write("!! %s\n" % task.title()) def cleanup_error(self, exception): """error during cleanup""" sys.stderr.write(exception.get_msg()) def runtime_error(self, msg): """error from doit (not from a task execution)""" # saved so they are displayed after task failures messages self.runtime_errors.append(msg) def teardown_task(self, task): """called when starts the execution of teardown action""" pass def _write_failure(self, result, write_exception=True): msg = '%s - taskid:%s\n' % (result['exception'].get_name(), result['task'].name) self.write(msg) if write_exception: self.write(result['exception'].get_msg()) self.write("\n") def complete_run(self): """called when finished running all tasks""" # if test fails print output from failed task for result in self.failures: task = result['task'] # makes no sense to print output if task was not executed if not task.executed: continue show_err = task.verbosity < 1 or self.failure_verbosity > 0 show_out = task.verbosity < 2 or self.failure_verbosity == 2 if show_err or show_out: self.write("#" * 40 + "\n") if show_err: self._write_failure(result, write_exception=self.failure_verbosity) err = "".join([a.err for a in task.actions if a.err]) self.write("{} :\n{}\n".format(task.name, err)) if show_out: out = "".join([a.out for a in task.actions if a.out]) self.write("{} :\n{}\n".format(task.name, out)) if self.runtime_errors: self.write("#" * 40 + "\n") self.write("Execution aborted.\n") self.write("\n".join(self.runtime_errors)) self.write("\n") class ExecutedOnlyReporter(ConsoleReporter): """No output for skipped (up-to-date) and group tasks Produces zero output unless a task is executed """ desc = 'console, no output for skipped (up-to-date) and group tasks' def skip_uptodate(self, task): """skipped up-to-date task""" pass def skip_ignore(self, task): """skipped ignored task""" pass class ZeroReporter(ConsoleReporter): """Report only internal errors from doit""" desc = 'report only internal errors from doit' def _just_pass(self, *args): """over-write base to do nothing""" pass get_status = execute_task = add_failure = add_success \ = skip_uptodate = skip_ignore = teardown_task = complete_run \ = _just_pass def runtime_error(self, msg): sys.stderr.write(msg) class ErrorOnlyReporter(ZeroReporter): desc = """Report only errors internal or TaskError and TaskFailure.""" def add_failure(self, task, fail_info: BaseFail): if not fail_info.report: return exception_name = fail_info.get_name() self.write(f'taskid:{task.name} - {exception_name}\n') self.write(fail_info.get_msg()) self.write("\n") class TaskResult(object): """result object used by JsonReporter""" # FIXME what about returned value from python-actions ? def __init__(self, task): self.task = task self.result = None # fail, success, up-to-date, ignore self.out = None # stdout from task self.err = None # stderr from task self.error = None # error from doit (exception traceback) self.started = None # datetime when task execution started self.elapsed = None # time (in secs) taken to execute task self._started_on = None # timestamp self._finished_on = None # timestamp def start(self): """called when task starts its execution""" self._started_on = time.time() def set_result(self, result, error=None): """called when task finishes its execution""" self._finished_on = time.time() self.result = result line_sep = "\n<------------------------------------------------>\n" self.out = line_sep.join([a.out for a in self.task.actions if a.out]) self.err = line_sep.join([a.err for a in self.task.actions if a.err]) self.error = error def to_dict(self): """convert result data to dictionary""" if self._started_on is not None: started = datetime.datetime.utcfromtimestamp(self._started_on) self.started = str(started.strftime('%Y-%m-%d %H:%M:%S.%f')) self.elapsed = self._finished_on - self._started_on return {'name': self.task.name, 'result': self.result, 'out': self.out, 'err': self.err, 'error': self.error, 'started': self.started, 'elapsed': self.elapsed} class JsonReporter(object): """output results in JSON format - out (str) - err (str) - tasks (list - dict): - name (str) - result (str) - out (str) - err (str) - error (str) - started (str) - elapsed (float) """ desc = 'output in JSON format' def __init__(self, outstream, options=None): # pylint: disable=W0613 # options parameter is not used # json result is sent to stdout when doit finishes running self.t_results = {} # when using json reporter output can not contain any other output # than the json data. so anything that is sent to stdout/err needs to # be captured. self._old_out = sys.stdout sys.stdout = StringIO() self._old_err = sys.stderr sys.stderr = StringIO() self.outstream = outstream # runtime and cleanup errors self.errors = [] def get_status(self, task): """called when task is selected (check if up-to-date)""" self.t_results[task.name] = TaskResult(task) def execute_task(self, task): """called when execution starts""" self.t_results[task.name].start() def add_failure(self, task, exception): """called when execution finishes with a failure""" self.t_results[task.name].set_result('fail', exception.get_msg()) def add_success(self, task): """called when execution finishes successfully""" self.t_results[task.name].set_result('success') def skip_uptodate(self, task): """skipped up-to-date task""" self.t_results[task.name].set_result('up-to-date') def skip_ignore(self, task): """skipped ignored task""" self.t_results[task.name].set_result('ignore') def cleanup_error(self, exception): """error during cleanup""" self.errors.append(exception.get_msg()) def runtime_error(self, msg): """error from doit (not from a task execution)""" self.errors.append(msg) def teardown_task(self, task): """called when starts the execution of teardown action""" pass def complete_run(self): """called when finished running all tasks""" # restore stdout log_out = sys.stdout.getvalue() sys.stdout = self._old_out log_err = sys.stderr.getvalue() sys.stderr = self._old_err # add errors together with stderr output if self.errors: log_err += "\n".join(self.errors) task_result_list = [ tr.to_dict() for tr in self.t_results.values()] json_data = {'tasks': task_result_list, 'out': log_out, 'err': log_err} # indent not available on simplejson 1.3 (debian etch) # json.dump(json_data, sys.stdout, indent=4) json.dump(json_data, self.outstream) doit-0.36.0/doit/runner.py000066400000000000000000000475231423054503100153710ustar00rootroot00000000000000"""Task runner""" from multiprocessing import Process, Queue as MQueue from threading import Thread import pickle import queue import cloudpickle from .exceptions import InvalidTask, BaseFail from .exceptions import TaskFailed, SetupError, DependencyError, UnmetDependency from .task import Stream, DelayedLoaded # execution result. SUCCESS = 0 FAILURE = 1 ERROR = 2 class Runner(): """Task runner run_all() run_tasks(): for each task: select_task() execute_task() process_task_result() finish() """ def __init__(self, dep_manager, reporter, continue_=False, always_execute=False, stream=None): """ @param dep_manager: DependencyBase @param reporter: reporter object to be used @param continue_: (bool) execute all tasks even after a task failure @param always_execute: (bool) execute even if up-to-date or ignored @param stream: (task.Stream) global verbosity """ self.dep_manager = dep_manager self.reporter = reporter self.continue_ = continue_ self.always_execute = always_execute self.stream = stream if stream else Stream(0) self.teardown_list = [] # list of tasks to be teardown self.final_result = SUCCESS # until something fails self._stop_running = False def _handle_task_error(self, node, base_fail): """handle all task failures/errors called whenever there is an error before executing a task or its execution is not successful. """ assert isinstance(base_fail, BaseFail) node.run_status = "failure" self.dep_manager.remove_success(node.task) self.reporter.add_failure(node.task, base_fail) # only return FAILURE if no errors happened. if isinstance(base_fail, TaskFailed) and self.final_result != ERROR: self.final_result = FAILURE else: self.final_result = ERROR if not self.continue_: self._stop_running = True def _get_task_args(self, task, tasks_dict): """get values from other tasks""" task.init_options() def get_value(task_id, key_name): """get single value or dict from task's saved values""" if key_name is None: return self.dep_manager.get_values(task_id) return self.dep_manager.get_value(task_id, key_name) # selected just need to get values from other tasks for arg, value in task.getargs.items(): task_id, key_name = value if tasks_dict[task_id].has_subtask: # if a group task, pass values from all sub-tasks arg_value = {} base_len = len(task_id) + 1 # length of base name string for sub_id in tasks_dict[task_id].task_dep: name = sub_id[base_len:] arg_value[name] = get_value(sub_id, key_name) else: arg_value = get_value(task_id, key_name) task.options[arg] = arg_value def select_task(self, node, tasks_dict): """Returns bool, task should be executed * side-effect: set task.options Tasks should be executed if they are not up-to-date. Tasks that contains setup-tasks must be selected twice, so it gives chance for dependency tasks to be executed after checking it is not up-to-date. """ task = node.task # if run_status is not None, it was already calculated if node.run_status is None: self.reporter.get_status(task) # overwrite with effective verbosity task.overwrite_verbosity(self.stream) # check if task should be ignored (user controlled) if node.ignored_deps or self.dep_manager.status_is_ignore(task): node.run_status = 'ignore' self.reporter.skip_ignore(task) return False # check task_deps if node.bad_deps: bad_str = " ".join(n.task.name for n in node.bad_deps) self._handle_task_error(node, UnmetDependency(bad_str)) return False # check if task is up-to-date res = self.dep_manager.get_status(task, tasks_dict) if res.status == 'error': msg = "ERROR: Task '{}' checking dependencies: {}".format( task.name, res.get_error_message()) self._handle_task_error(node, DependencyError(msg)) return False # set node.run_status if self.always_execute: node.run_status = 'run' else: node.run_status = res.status # if task is up-to-date skip it if node.run_status == 'up-to-date': self.reporter.skip_uptodate(task) task.values = self.dep_manager.get_values(task.name) return False if task.setup_tasks: # dont execute now, execute setup first... return False else: # sanity checks assert node.run_status == 'run', \ "%s:%s" % (task.name, node.run_status) assert task.setup_tasks try: self._get_task_args(task, tasks_dict) except Exception as exception: msg = ("ERROR getting value for argument\n" + str(exception)) self._handle_task_error(node, DependencyError(msg)) return False return True def execute_task(self, task): """execute task's actions""" # register cleanup/teardown if task.teardown: self.teardown_list.append(task) # finally execute it! self.reporter.execute_task(task) return task.execute(self.stream) def process_task_result(self, node, base_fail): """handles result""" task = node.task # save execution successful if base_fail is None: task.save_extra_values() try: self.dep_manager.save_success(task) except FileNotFoundError as exception: msg = (f"ERROR: Task '{task.name}' saving success: " f"Dependent file '{exception.filename}' does not exist.") base_fail = DependencyError(msg) else: node.run_status = "successful" self.reporter.add_success(task) return # task error self._handle_task_error(node, base_fail) def run_tasks(self, task_dispatcher): """This will actually run/execute the tasks. It will check file dependencies to decide if task should be executed and save info on successful runs. It also deals with output to stdout/stderr. @param task_dispatcher: L{TaskDispacher} """ node = None while True: if self._stop_running: break try: node = task_dispatcher.generator.send(node) except StopIteration: break if not self.select_task(node, task_dispatcher.tasks): continue base_fail = self.execute_task(node.task) self.process_task_result(node, base_fail) def teardown(self): """run teardown from all tasks""" for task in reversed(self.teardown_list): self.reporter.teardown_task(task) result = task.execute_teardown(self.stream) if result: msg = "ERROR: task '%s' teardown action" % task.name error = SetupError(msg, result) self.reporter.cleanup_error(error) def finish(self): """finish running tasks""" # flush update dependencies self.dep_manager.close() self.teardown() # report final results self.reporter.complete_run() return self.final_result def run_all(self, task_dispatcher): """entry point to run tasks @ivar task_dispatcher (TaskDispatcher) """ try: if hasattr(self.reporter, 'initialize'): self.reporter.initialize(task_dispatcher.tasks, task_dispatcher.selected_tasks) self.run_tasks(task_dispatcher) except InvalidTask as exception: self.reporter.runtime_error(str(exception)) self.final_result = ERROR finally: self.finish() return self.final_result # JobXXX objects send from main process to sub-process for execution class JobHold(object): """Indicates there is no task ready to be executed""" type = object() class JobTask(object): """Contains a Task object""" type = object() def __init__(self, task): self.name = task.name try: self.task_pickle = cloudpickle.dumps(task) except pickle.PicklingError as excp: msg = """Error on Task: `{}`. Task created at execution time that has an attribute than can not be pickled, so not feasible to be used with multi-processing. To fix this issue make sure the task is pickable or just do not use multi-processing execution. Original exception {}: {} """ raise InvalidTask(msg.format(self.name, excp.__class__, excp)) class JobTaskPickle(object): """dict of Task object excluding attributes that might be unpicklable""" type = object() def __init__(self, task): # actually a dict to be pickled self.task_dict = task.pickle_safe_dict() @property def name(self): return self.task_dict['name'] class MReporter(object): """send reported messages to master process puts a dictionary {'name': , 'reporter': } on runner's 'result_q' """ def __init__(self, runner, reporter_cls): self.runner = runner self.reporter_cls = reporter_cls def __getattr__(self, method_name): """substitute any reporter method with a dispatching method""" if not hasattr(self.reporter_cls, method_name): raise AttributeError(method_name) def rep_method(task): self.runner.result_q.put({ 'name': task.name, 'reporter': method_name, }) return rep_method def complete_run(self): """ignore this on MReporter""" pass class MRunner(Runner): """MultiProcessing Runner """ Queue = staticmethod(MQueue) Child = staticmethod(Process) @staticmethod def available(): """check if multiprocessing module is available""" # see: https://bitbucket.org/schettino72/doit/issue/17 # http://bugs.python.org/issue3770 # not available on BSD systens try: import multiprocessing.synchronize multiprocessing # pyflakes except ImportError: # pragma: no cover return False else: return True def __init__(self, dep_manager, reporter, continue_=False, always_execute=False, stream=None, num_process=1): Runner.__init__(self, dep_manager, reporter, continue_=continue_, always_execute=always_execute, stream=stream) self.num_process = num_process self.free_proc = 0 # number of free process self.task_dispatcher = None # TaskDispatcher retrieve tasks self.tasks = None # dict of task instances by name self.result_q = None def __getstate__(self): # multiprocessing on Windows will try to pickle self. # These attributes are actually not used by spawend process so # safe to be removed. pickle_dict = self.__dict__.copy() pickle_dict['reporter'] = None pickle_dict['task_dispatcher'] = None pickle_dict['dep_manager'] = None return pickle_dict def get_next_job(self, completed): """get next task to be dispatched to sub-process On MP needs to check if the dependencies finished its execution @returns : - None -> no more tasks to be executed - JobXXX """ if self._stop_running: return None # gentle stop node = completed while True: # get next task from controller try: node = self.task_dispatcher.generator.send(node) if node == "hold on": self.free_proc += 1 return JobHold() # no more tasks from controller... except StopIteration: # ... terminate one sub process if no other task waiting return None # send a task to be executed if self.select_task(node, self.tasks): # If sub-process already contains the Task object send # only safe pickle data, otherwise send whole object. task = node.task if task.loader is DelayedLoaded and self.Child == Process: return JobTask(task) else: return JobTaskPickle(task) def _run_tasks_init(self, task_dispatcher): """initialization for run_tasks""" self.task_dispatcher = task_dispatcher self.tasks = task_dispatcher.tasks def _run_start_processes(self, job_q, result_q): """create and start sub-processes @param job_q: (multiprocessing.Queue) tasks to be executed @param result_q: (multiprocessing.Queue) collect task results @return list of Process """ # #### DEBUG PICKLE ERRORS # class MyPickler (pickle._Pickler): # def save(self, obj): # print('pickling object {} of type {}'.format(obj, type(obj))) # try: # Pickler.save(self, obj) # except: # print('error. skipping...') # from io import BytesIO # pickler = MyPickler(BytesIO()) # pickler.dump(self) # ### END DEBUG proc_list = [] for _ in range(self.num_process): next_job = self.get_next_job(None) if next_job is None: break # do not start more processes than tasks job_q.put(next_job) process = self.Child( target=self.execute_task_subprocess, args=(job_q, result_q, self.reporter.__class__)) process.start() proc_list.append(process) return proc_list def _process_result(self, node, task, result): """process result received from sub-process""" base_fail = result.get('failure') task.update_from_pickle(result['task']) for action, output in zip(task.actions, result['out']): action.out = output for action, output in zip(task.actions, result['err']): action.err = output self.process_task_result(node, base_fail) def run_tasks(self, task_dispatcher): """controls subprocesses task dispatching and result collection """ # result queue - result collected from sub-processes result_q = self.Queue() # task queue - tasks ready to be dispatched to sub-processes job_q = self.Queue() self._run_tasks_init(task_dispatcher) proc_list = self._run_start_processes(job_q, result_q) # wait for all processes terminate proc_count = len(proc_list) try: while proc_count: # wait until there is a result to be consumed result = result_q.get() if 'exit' in result: raise result['exit'](result['exception']) node = task_dispatcher.nodes[result['name']] task = node.task if 'reporter' in result: getattr(self.reporter, result['reporter'])(task) continue self._process_result(node, task, result) # update num free process free_proc = self.free_proc + 1 self.free_proc = 0 # tries to get as many tasks as free process completed = node for _ in range(free_proc): next_job = self.get_next_job(completed) completed = None if next_job is None: proc_count -= 1 job_q.put(next_job) # check for cyclic dependencies assert len(proc_list) > self.free_proc except (SystemExit, KeyboardInterrupt, Exception): if self.Child == Process: for proc in proc_list: proc.terminate() raise # we are done, join all process for proc in proc_list: proc.join() # get teardown results while not result_q.empty(): # safe because subprocess joined result = result_q.get() assert 'reporter' in result task = task_dispatcher.tasks[result['name']] getattr(self.reporter, result['reporter'])(task) def execute_task_subprocess(self, job_q, result_q, reporter_class): """executed on child processes @param job_q: task queue, * None elements indicate process can terminate * JobHold indicate process should wait for next task * JobTask / JobTaskPickle task to be executed """ self.result_q = result_q if self.Child == Process: self.reporter = MReporter(self, reporter_class) try: while True: job = job_q.get() if job is None: self.teardown() return # no more tasks to execute finish this process # job is an incomplete Task obj when pickled, attrbiutes # that might contain unpickleble data were removed. # so we need to get task from this process and update it # to get dynamic task attributes. if job.type is JobTaskPickle.type: task = self.tasks[job.name] if self.Child == Process: # pragma: no cover ... # ... actually covered but subprocess doesnt get it. task.update_from_pickle(job.task_dict) elif job.type is JobTask.type: task = pickle.loads(job.task_pickle) # do nothing. this is used to start the subprocess even # if no task is available when process is created. else: assert job.type is JobHold.type continue # pragma: no cover result = {'name': task.name} task_failure = self.execute_task(task) if task_failure: result['failure'] = task_failure result['task'] = task.pickle_safe_dict() result['out'] = [action.out for action in task.actions] result['err'] = [action.err for action in task.actions] result_q.put(result) except (SystemExit, KeyboardInterrupt, Exception) as exception: # error, blow-up everything. send exception info to master process result_q.put({ 'exit': exception.__class__, 'exception': str(exception)}) class MThreadRunner(MRunner): """Parallel runner using threads""" Queue = staticmethod(queue.Queue) class DaemonThread(Thread): """daemon thread to make sure process is terminated if there is an uncatch exception and threads are not correctly joined. """ def __init__(self, *args, **kwargs): Thread.__init__(self, *args, **kwargs) self.daemon = True Child = staticmethod(DaemonThread) @staticmethod def available(): return True doit-0.36.0/doit/task.py000066400000000000000000000574411423054503100150220ustar00rootroot00000000000000 """Tasks are the main abstractions managed by doit""" import os import sys import inspect from collections import OrderedDict from collections.abc import Callable from pathlib import PurePath from .cmdparse import CmdOption, TaskParse from .exceptions import BaseFail, InvalidTask from .action import create_action, PythonAction from .dependency import UptodateCalculator def first_line(doc): """extract first non-blank line from text, to extract docstring title""" if doc is not None: for line in doc.splitlines(): striped = line.strip() if striped: return striped return '' class DelayedLoader(object): """contains info for delayed creation of tasks from a task-creator :ivar creator: reference to task-creator function :ivar task_dep: (str) name of task that should be executed before the the loader call the creator function :ivar basename: (str) basename used when creating tasks This is used when doit creates new tasks to handle tasks and targets specified on command line :ivar target_regex: (str) regex for all targets that this loader tasks will create :ivar created: (bool) whether this creator was already executed or not """ def __init__(self, creator, executed=None, target_regex=None, creates=None): self.creator = creator self.task_dep = executed self.basename = None self.created = False self.target_regex = target_regex self.creates = creates[:] if creates else [] self.regex_groups = OrderedDict() # task_name:RegexGroup self.kwargs = None # task creator kwargs # used to indicate that a task had DelayedLoader but was already created DelayedLoaded = False class Stream(): """Control task output stream verbosity :ivar verbosity: 0,1,2 see Task.execute Priority is given by: 1) command line -> force_global=True 2) task value 3) other config (INI, DOIT_CONFIG) """ def __init__(self, verbosity, force_global=False): self.force_global = force_global if verbosity is not None: self.verbosity = verbosity else: self.verbosity = Task.DEFAULT_VERBOSITY self.force_global = False def effective_verbosity(self, task_verbosity): """return effective verbosity used on task""" if self.force_global: return self.verbosity elif task_verbosity is not None: return task_verbosity else: return self.verbosity @staticmethod def _get_out_err(verbosity): """return tuple (out, err) streams to be used Replace stream with None if stream should be captured :param verbosity: (int) """ if verbosity == 0: return (None, None) elif verbosity == 1: return (None, sys.stderr) else: return (sys.stdout, sys.stderr) class IOConfig: def __init__(self, io_data): self.capture = io_data.get('capture', True) def __repr__(self): return f'IOConfig(capture={self.capture})' class Task(object): """Task @ivar name string @ivar actions: list - L{BaseAction} @ivar clean_actions: list - L{BaseAction} @ivar loader (DelayedLoader) @ivar teardown (list - L{BaseAction}) @ivar targets: (list -string) @ivar task_dep: (list - string) @ivar wild_dep: (list - string) task dependency using wildcard * @ivar file_dep: (set - string) @ivar calc_dep: (set - string) reference to a task @ivar dep_changed (list - string): list of file-dependencies that changed (are not up_to_date). this must be set before @ivar uptodate: (list - bool/None) use bool/computed value instead of checking dependencies @ivar value_savers (list - callables) that return dicts to be added to task values. Always executed on main process. To be used by `uptodate` implementations. @ivar setup_tasks (list - string): references to task-names @ivar subtask_of: (string) indicate this task is a subtask of task name @ivar has_subtask: (bool) indicate this task has subtasks @ivar result: (str) last action "result". used to check task-result-dep @ivar values: (dict) values saved by task that might be used by other tasks @ivar getargs: (dict) values from other tasks @ivar doc: (string) task documentation @ivar meta: (dict) extra info from user/plugin not directly used by doit @ivar options: (dict) calculated params values (from getargs and taskopt) @ivar taskopt: (cmdparse.CmdParse) @ivar pos_arg: (str) name of parameter in action to receive positional parameters from command line @ivar pos_arg_val: (list - str) list of positional parameters values @ivar custom_title: function reference that takes a task object as parameter and returns a string. """ DEFAULT_VERBOSITY = 1 string_types = (str, ) # list of valid types/values for each task attribute. valid_attr = {'basename': (string_types, ()), 'name': (string_types, ()), 'actions': ((list, tuple), (None,)), 'file_dep': ((list, tuple), ()), 'task_dep': ((list, tuple), ()), 'uptodate': ((list, tuple), ()), 'calc_dep': ((list, tuple), ()), 'targets': ((list, tuple), ()), 'setup': ((list, tuple), ()), 'clean': ((list, tuple), (True,)), 'teardown': ((list, tuple), ()), 'doc': (string_types, (None,)), 'params': ((list, tuple,), ()), 'pos_arg': (string_types, (None,)), 'verbosity': ((), (None, 0, 1, 2,)), 'io': ((dict,), (None,)), 'getargs': ((dict,), ()), 'title': ((Callable,), (None,)), 'watch': ((list, tuple), ()), 'meta': ((dict,), (None,)) } def __init__(self, name, actions, file_dep=(), targets=(), task_dep=(), uptodate=(), calc_dep=(), setup=(), clean=(), teardown=(), subtask_of=None, has_subtask=False, doc=None, params=(), pos_arg=None, verbosity=None, io=None, title=None, getargs=None, watch=(), meta=None, loader=None): """sanity checks and initialization @param params: (list of dict for parameters) see cmdparse.CmdOption """ getargs = getargs or {} # default self.check_attr(name, 'name', name, self.valid_attr['name']) self.check_attr(name, 'actions', actions, self.valid_attr['actions']) self.check_attr(name, 'file_dep', file_dep, self.valid_attr['file_dep']) self.check_attr(name, 'task_dep', task_dep, self.valid_attr['task_dep']) self.check_attr(name, 'uptodate', uptodate, self.valid_attr['uptodate']) self.check_attr(name, 'calc_dep', calc_dep, self.valid_attr['calc_dep']) self.check_attr(name, 'targets', targets, self.valid_attr['targets']) self.check_attr(name, 'setup', setup, self.valid_attr['setup']) self.check_attr(name, 'clean', clean, self.valid_attr['clean']) self.check_attr(name, 'teardown', teardown, self.valid_attr['teardown']) self.check_attr(name, 'doc', doc, self.valid_attr['doc']) self.check_attr(name, 'params', params, self.valid_attr['params']) self.check_attr(name, 'pos_arg', pos_arg, self.valid_attr['pos_arg']) self.check_attr(name, 'verbosity', verbosity, self.valid_attr['verbosity']) self.check_attr(name, 'io', io, self.valid_attr['io']) self.check_attr(name, 'getargs', getargs, self.valid_attr['getargs']) self.check_attr(name, 'title', title, self.valid_attr['title']) self.check_attr(name, 'watch', watch, self.valid_attr['watch']) self.check_attr(name, 'meta', meta, self.valid_attr['meta']) if '=' in name: msg = "Task '{}': name must not use the char '=' (equal sign)." raise InvalidTask(msg.format(name)) self.name = name self.params = params # save just for use on command `info` self.creator_params = [] # add through task_params decorator self.options = None self.pos_arg = pos_arg self.pos_arg_val = None # to be set when parsing command line self.setup_tasks = list(setup) # actions self.io = IOConfig(io or {}) self._action_instances = None if actions is None: self._actions = [] else: self._actions = list(actions[:]) self._init_deps(file_dep, task_dep, calc_dep) # loaders create an implicity task_dep self.loader = loader if self.loader and self.loader.task_dep: self.task_dep.append(loader.task_dep) uptodate = uptodate if uptodate else [] self.getargs = getargs if self.getargs: uptodate.extend(self._init_getargs()) self.value_savers = [] self.uptodate = self._init_uptodate(uptodate) self.targets = self._init_targets(targets) self.subtask_of = subtask_of self.has_subtask = has_subtask self.result = None self.values = {} self.verbosity = verbosity self.custom_title = title self.cfg_values = None # clean if clean is True: self._remove_targets = True self.clean_actions = () else: self._remove_targets = False self.clean_actions = [create_action(a, self, 'clean') for a in clean] self.teardown = [create_action(a, self, 'teardown') for a in teardown] self.doc = self._init_doc(doc) self.watch = watch self.meta = meta # just indicate if actions were executed at all self.executed = False def _init_deps(self, file_dep, task_dep, calc_dep): """init for dependency related attributes""" self.dep_changed = None # file_dep self.file_dep = set() self._expand_file_dep(file_dep) # task_dep self.task_dep = [] self.wild_dep = [] if task_dep: self._expand_task_dep(task_dep) # calc_dep self.calc_dep = set() if calc_dep: self._expand_calc_dep(calc_dep) def _init_targets(self, items): """convert valid targets to `str`""" targets = [] for target in items: if isinstance(target, str): targets.append(target) elif isinstance(target, PurePath): targets.append(str(target)) else: msg = ("%s. target must be a str or Path from pathlib. Got '%r' (%s)") raise InvalidTask(msg % (self.name, target, type(target))) return targets def _init_uptodate(self, items): """wrap uptodate callables""" uptodate = [] for item in items: # configure task if hasattr(item, 'configure_task'): item.configure_task(self) # check/append uptodate value to task if isinstance(item, bool) or item is None: uptodate.append((item, None, None)) elif hasattr(item, '__call__'): uptodate.append((item, [], {})) elif isinstance(item, tuple): call = item[0] args = list(item[1]) if len(item) > 1 else [] kwargs = item[2] if len(item) > 2 else {} uptodate.append((call, args, kwargs)) elif isinstance(item, str): uptodate.append((item, [], {})) else: msg = ("%s. task invalid 'uptodate' item '%r'. " "Must be bool, None, str, callable or tuple " "(callable, args, kwargs).") raise InvalidTask(msg % (self.name, item)) return uptodate def _expand_file_dep(self, file_dep): """put input into file_dep""" for dep in file_dep: if isinstance(dep, str): self.file_dep.add(dep) elif isinstance(dep, PurePath): self.file_dep.add(str(dep)) else: msg = ("%s. file_dep must be a str or Path from pathlib. " "Got '%r' (%s)") raise InvalidTask(msg % (self.name, dep, type(dep))) def _expand_task_dep(self, task_dep): """convert task_dep input into actaul task_dep and wild_dep""" for dep in task_dep: if "*" in dep: self.wild_dep.append(dep) else: self.task_dep.append(dep) def _expand_calc_dep(self, calc_dep): """calc_dep input""" for dep in calc_dep: if dep not in self.calc_dep: self.calc_dep.add(dep) def _extend_uptodate(self, uptodate): """add/extend uptodate values""" self.uptodate.extend(self._init_uptodate(uptodate)) # FIXME should support setup also _expand_map = { 'task_dep': _expand_task_dep, 'file_dep': _expand_file_dep, 'calc_dep': _expand_calc_dep, 'uptodate': _extend_uptodate, } def update_deps(self, deps): """expand all kinds of dep input""" for dep, dep_values in deps.items(): if dep not in self._expand_map: continue self._expand_map[dep](self, dep_values) def init_options(self, args=None): """Put default values on options. This function will only initialize task options once. If provided the args parameter will be parsed for command line arguments intended for this task. Return value: unparsed command line task arguments or None. """ if self.options is None: self.options = {} all_opts = list(self.params) + self.creator_params taskcmd = TaskParse([CmdOption(opt) for opt in all_opts]) if self.cfg_values is not None: taskcmd.overwrite_defaults(self.cfg_values) if args is None or len(args) == 0: # ignore positional parameters self.options.update(taskcmd.parse('')[0]) elif len(args) > 0: parsed_options, args = taskcmd.parse(args) self.options.update(parsed_options) return args def _init_getargs(self): """task getargs attribute define implicit task dependencies""" check_result = set() for arg_name, desc in self.getargs.items(): # tuple (task_id, key_name) parts = desc if isinstance(parts, str) or len(parts) != 2: raise InvalidTask( f"Taskid '{self.name}' - Invalid format for getargs of '{arg_name}'." "Should be tuple with 2 elements" f" ('', '') got '{desc}'") if parts[0] not in self.setup_tasks: check_result.add(parts[0]) return [result_dep(t, setup_dep=True) for t in check_result] @staticmethod def _init_doc(doc): """process task "doc" attribute""" # store just first non-empty line as documentation string return first_line(doc) @staticmethod def check_attr(task, attr, value, valid): """check input task attribute is correct type/value @param task (string): task name @param attr (string): attribute name @param value: actual input from user @param valid (list): of valid types/value accepted @raises InvalidTask if invalid input """ if isinstance(value, valid[0]): return if value in valid[1]: return # input value didnt match any valid type/value, raise exception msg = "Task '%s' attribute '%s' must be " % (task, attr) accept = ", ".join([getattr(v, '__name__', str(v)) for v in (valid[0] + valid[1])]) msg += "{%s} got:%r %s" % (accept, value, type(value)) raise InvalidTask(msg) @property def actions(self): """lazy creation of action instances""" if self._action_instances is None: self._action_instances = [ create_action(a, self, 'actions') for a in self._actions] return self._action_instances def save_extra_values(self): """run value_savers updating self.values""" for value_saver in self.value_savers: self.values.update(value_saver()) def overwrite_verbosity(self, stream): self.verbosity = stream.effective_verbosity(self.verbosity) def execute(self, stream): """Executes the task. @return failure: see CmdAction.execute """ self.executed = True self.init_options() task_stdout, task_stderr = stream._get_out_err(self.verbosity) for action in self.actions: action_return = action.execute(task_stdout, task_stderr) if isinstance(action_return, BaseFail): return action_return self.result = action.result self.values.update(action.values) def execute_teardown(self, stream): """Executes task's teardown @return failure: see CmdAction.execute """ task_stdout, task_stderr = stream._get_out_err(self.verbosity) for action in self.teardown: action_return = action.execute(task_stdout, task_stderr) if isinstance(action_return, BaseFail): return action_return def clean(self, outstream, dryrun): """Execute task's clean @ivar outstream: 'write' output into this stream @ivar dryrun (bool): if True clean tasks are not executed (just print out what would be executed) """ self.init_options() # if clean is True remove all targets if self._remove_targets is True: clean_targets(self, dryrun) else: # clean contains a list of actions... for action in self.clean_actions: msg = "%s - executing '%s'\n" outstream.write(msg % (self.name, action)) # add extra arguments used by clean actions execute_on_dryrun = False if isinstance(action, PythonAction): action_sig = inspect.signature(action.py_callable) if 'dryrun' in action_sig.parameters: execute_on_dryrun = True action.kwargs['dryrun'] = dryrun if (not dryrun) or execute_on_dryrun: result = action.execute(out=outstream) if isinstance(result, BaseFail): sys.stderr.write(str(result)) def title(self): """String representation on output. @return: (str) Task name and actions """ if self.custom_title: return self.custom_title(self) return self.name def __repr__(self): return f"" def __getstate__(self): """remove attributes that never used on process that only execute tasks """ to_pickle = self.__dict__.copy() # never executed in sub-process to_pickle['uptodate'] = None to_pickle['value_savers'] = None # can be re-recreated on demand to_pickle['_action_instances'] = None return to_pickle # when using multiprocessing Tasks are pickled. def pickle_safe_dict(self): """remove attributes that might contain unpickleble content mostly probably closures """ to_pickle = self.__dict__.copy() del to_pickle['_actions'] del to_pickle['_action_instances'] del to_pickle['clean_actions'] del to_pickle['teardown'] del to_pickle['custom_title'] del to_pickle['value_savers'] del to_pickle['uptodate'] return to_pickle def update_from_pickle(self, pickle_obj): """update self with data from pickled Task""" self.__dict__.update(pickle_obj) def __eq__(self, other): return self.name == other.name def __lt__(self, other): """used on default sorting of tasks (alphabetically by name)""" return self.name < other.name def dict_to_task(task_dict): """Create a task instance from dictionary. The dictionary has the same format as returned by task-generators from dodo files. @param task_dict (dict): task representation as a dict. @raise InvalidTask: If unexpected fields were passed in task_dict """ # check required fields if 'actions' not in task_dict: raise InvalidTask("Task %s must contain 'actions' field. %s" % (task_dict['name'], task_dict)) # user friendly. dont go ahead with invalid input. task_attrs = list(task_dict.keys()) valid_attrs = set(Task.valid_attr.keys()) for key in task_attrs: if key not in valid_attrs: name = task_dict['name'] raise InvalidTask(f"Task {name} contains invalid field: '{key}'") return Task(**task_dict) def clean_targets(task, dryrun): """remove all targets from a task""" for target in sorted(task.targets, reverse=True): if os.path.isfile(target): print("%s - removing file '%s'" % (task.name, target)) if not dryrun: os.remove(target) elif os.path.isdir(target): if os.listdir(target): msg = "%s - cannot remove (it is not empty) '%s'" print(msg % (task.name, target)) else: msg = "%s - removing dir '%s'" print(msg % (task.name, target)) if not dryrun: os.rmdir(target) # uptodate class result_dep(UptodateCalculator): """check if result of the given task was modified """ def __init__(self, dep_task_name, setup_dep=False): ''' :param setup_dep: controls if dependent task is task_dep or setup ''' self.dep_name = dep_task_name self.setup_dep = setup_dep self.result_name = '_result:%s' % self.dep_name def configure_task(self, task): """to be called by doit when create the task""" # result_dep creates an implicit task_dep if self.setup_dep: task.setup_tasks.append(self.dep_name) else: task.task_dep.append(self.dep_name) def _result_single(self): """get result from a single task""" return self.get_val(self.dep_name, 'result:') def _result_group(self, dep_task): """get result from a group task the result is the combination of results of all sub-tasks """ prefix = dep_task.name + ":" sub_tasks = {} for sub in dep_task.task_dep: if sub.startswith(prefix): sub_tasks[sub] = self.get_val(sub, 'result:') return sub_tasks def _get_dep_result(self, dep_task): if not dep_task.has_subtask: dep_result = self._result_single() else: dep_result = self._result_group(dep_task) return dep_result def __call__(self, task, values): """return True if result is the same as last run""" dep_task = self.tasks_dict[self.dep_name] dep_result = self._get_dep_result(dep_task) def result_saver(): # get latest value after execution of dependent task return {self.result_name: self._get_dep_result(dep_task)} task.value_savers.append(result_saver) last_success = values.get(self.result_name) if last_success is None: return False return last_success == dep_result doit-0.36.0/doit/tools.py000066400000000000000000000235651423054503100152200ustar00rootroot00000000000000"""extra goodies to be used in dodo files""" import os import time as time_module import datetime import json import hashlib import operator import subprocess from . import exceptions from .action import CmdAction, PythonAction from .task import result_dep # imported for backward compatibility result_dep # pyflakes # action def create_folder(dir_path): """create a folder in the given path if it doesnt exist yet.""" os.makedirs(dir_path, exist_ok=True) # title def title_with_actions(task): """return task name task actions""" if task.actions: title = "\n\t".join([str(action) for action in task.actions]) # A task that contains no actions at all # is used as group task else: title = "Group: %s" % ", ".join(task.task_dep) return "%s => %s" % (task.name, title) # uptodate def run_once(task, values): """execute task just once used when user manually manages a dependency """ def save_executed(): return {'run-once': True} task.value_savers.append(save_executed) return values.get('run-once', False) # uptodate class config_changed(object): """check if passed config was modified @var config (str) or (dict) @var encoder (json.JSONEncoder) Encoder used to convert non-default values. """ def __init__(self, config, encoder=None): self.config = config self.config_digest = None self.encoder = encoder def _calc_digest(self): if isinstance(self.config, str): return self.config elif isinstance(self.config, dict): data = json.dumps(self.config, sort_keys=True, cls=self.encoder) byte_data = data.encode("utf-8") return hashlib.md5(byte_data).hexdigest() else: msg = ('Invalid type of config_changed parameter got %s,' ' must be string or dict') raise Exception(msg % (type(self.config),)) def configure_task(self, task): task.value_savers.append(lambda: {'_config_changed': self.config_digest}) def __call__(self, task, values): """return True if config values are UNCHANGED""" self.config_digest = self._calc_digest() last_success = values.get('_config_changed') if last_success is None: return False return (last_success == self.config_digest) # uptodate class timeout(object): """add timeout to task @param timeout_limit: (datetime.timedelta, int) in seconds if the time elapsed since last time task was executed is bigger than the "timeout" time the task is NOT up-to-date """ def __init__(self, timeout_limit): if isinstance(timeout_limit, datetime.timedelta): self.limit_sec = ((timeout_limit.days * 24 * 3600) + timeout_limit.seconds) elif isinstance(timeout_limit, int): self.limit_sec = timeout_limit else: msg = "timeout should be datetime.timedelta or int got %r " raise Exception(msg % timeout_limit) def __call__(self, task, values): def save_now(): return {'success-time': time_module.time()} task.value_savers.append(save_now) last_success = values.get('success-time', None) if last_success is None: return False return (time_module.time() - last_success) < self.limit_sec # uptodate class check_timestamp_unchanged(object): """check if timestamp of a given file/dir is unchanged since last run. The C{cmp_op} parameter can be used to customize when timestamps are considered unchanged, e.g. you could pass L{operator.ge} to also consider e.g. files reverted to an older copy as unchanged; or pass a custom function to completely customize what unchanged means. If the specified file does not exist, an exception will be raised. Note that if the file C{fn} is a target of another task you should probably add C{task_dep} on that task to ensure the file is created before checking it. """ def __init__(self, file_name, time='mtime', cmp_op=operator.eq): """initialize the callable @param fn: (str) path to file/directory to check @param time: (str) which timestamp field to check, can be one of (atime, access, ctime, status, mtime, modify) @param cmp_op: (callable) takes two parameters (prev_time, current_time) should return True if the timestamp is considered unchanged @raises ValueError: if invalid C{time} value is passed """ if time in ('atime', 'access'): self._timeattr = 'st_atime' elif time in ('ctime', 'status'): self._timeattr = 'st_ctime' elif time in ('mtime', 'modify'): self._timeattr = 'st_mtime' else: raise ValueError('time can be one of: atime, access, ctime, ' 'status, mtime, modify (got: %r)' % time) self._file_name = file_name self._cmp_op = cmp_op self._key = '.'.join([self._file_name, self._timeattr]) def _get_time(self): return getattr(os.stat(self._file_name), self._timeattr) def __call__(self, task, values): """register action that saves the timestamp and check current timestamp @raises OSError: if cannot stat C{self._file_name} file (e.g. doesn't exist) """ def save_now(): return {self._key: self._get_time()} task.value_savers.append(save_now) prev_time = values.get(self._key) if prev_time is None: # this is first run return False current_time = self._get_time() return self._cmp_op(prev_time, current_time) # action class class LongRunning(CmdAction): """Action to handle a Long running shell process, usually a server or service. Properties: * the output is never captured * it is always successful (return code is not used) * "swallow" KeyboardInterrupt """ def execute(self, out=None, err=None): action = self.expand_action() process = subprocess.Popen(action, shell=self.shell, **self.pkwargs) try: process.wait() except KeyboardInterrupt: # normal way to stop interactive process pass # the name InteractiveAction is deprecated on 0.25 InteractiveAction = LongRunning class Interactive(CmdAction): """Action to handle Interactive shell process: * the output is never captured """ def execute(self, out=None, err=None): action = self.expand_action() process = subprocess.Popen(action, shell=self.shell, **self.pkwargs) process.wait() if process.returncode != 0: return exceptions.TaskFailed( "Interactive command failed: '%s' returned %s" % (action, process.returncode)) # action class class PythonInteractiveAction(PythonAction): """Action to handle Interactive python: * the output is never captured * it is successful unless a exception is raised """ def execute(self, out=None, err=None): kwargs = self._prepare_kwargs() try: returned_value = self.py_callable(*self.args, **kwargs) except Exception as exception: return exceptions.TaskError("PythonAction Error", exception) if isinstance(returned_value, str): self.result = returned_value elif isinstance(returned_value, dict): self.values = returned_value self.result = returned_value # debug helper def set_trace(): # pragma: no cover """start debugger, make sure stdout shows pdb output. output is not restored. """ import pdb import sys debugger = pdb.Pdb(stdin=sys.__stdin__, stdout=sys.__stdout__) debugger.set_trace(sys._getframe().f_back) # pylint: disable=W0212 def load_ipython_extension(ip=None): # pragma: no cover """ Defines a ``%doit`` magic function[1] that discovers and execute tasks from IPython's interactive variables (global namespace). It will fail if not invoked from within an interactive IPython shell. .. Tip:: To permanently add this magic-function to your IPython, create a new script inside your startup-profile (``~/.ipython/profile_default/startup/doit_magic.ipy``) with the following content: %load_ext doit %reload_ext doit %doit list [1] http://ipython.org/ipython-doc/dev/interactive/tutorial.html#magic-functions """ from IPython.core.getipython import get_ipython from IPython.core.magic import register_line_magic from doit.cmd_base import ModuleTaskLoader from doit.doit_cmd import DoitMain # Only (re)load_ext provides the ip context. ip = ip or get_ipython() @register_line_magic def doit(line): """ Run *doit* with `task_creators` from all interactive variables (IPython's global namespace). Examples: >>> %doit --help ## Show help for options and arguments. >>> def task_foo(): return {'actions': ['echo hi IPython'], 'verbosity': 2} >>> %doit list ## List any tasks discovered. foo >>> %doit ## Run any tasks. . foo hi IPython """ # Override db-files location inside ipython-profile dir, # which is certainly writable. prof_dir = ip.profile_dir.location opt_vals = {'dep_file': os.path.join(prof_dir, 'db', '.doit.db')} commander = DoitMain(ModuleTaskLoader(ip.user_module), extra_config={'GLOBAL': opt_vals}) commander.BIN_NAME = 'doit' commander.run(line.split()) # also expose another way of registering ipython extension register_doit_as_IPython_magic = load_ipython_extension doit-0.36.0/doit/version.py000066400000000000000000000001401423054503100155250ustar00rootroot00000000000000"""doit version, defined out of __init__.py to avoid circular reference""" VERSION = (0, 36, 0) doit-0.36.0/pylintrc000066400000000000000000000165121423054503100143300ustar00rootroot00000000000000[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). # :E1103: *%s %r has no %r member (but some types could not be inferred)* # :W0142: *Used * or ** magic* # :W0703: *Catch "Exception"* # :R0903: *Too few public methods (%s/%s)* # :R0922: *Abstract class is only referenced 1 times* # :E1101: *Used when a variable is accessed for an unexistent member. This message belongs to the typecheck checker.* (many false positive) disable=E1103,W0142,W0703,R0903,R0922,E1101 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=text # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (RP0004). comment=yes [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). ignored-classes=SQLObject # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. generated-members=REQUEST,acl_users,aq_parent [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the beginning of the name of dummy variables # (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [FORMAT] # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' [BASIC] # Required attributes for module, separated by a comma required-attributes= # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z0-9_]*)|[a-z_][a-z0-9_]{2,30}$|[A-Z_][a-zA-Z0-9]+$|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp doit-0.36.0/setup.cfg000066400000000000000000000007051423054503100143570ustar00rootroot00000000000000[pycodestyle] # E266 too many leading '#' for block comment # E301 expected 1 blank line # E302: expected 2 blank lines # E303: too many blank lines # E305: expected 2 blank lines after class or function definition # E306: expected 1 blank line before a nested definition # E731: do not assign lambda # W503: line break before binary operator ignore = E266, E301, E302, E303, E305, E306, E731, W503 max-line-length = 90 exclude = doit/cmd_completion.pydoit-0.36.0/setup.py000077500000000000000000000121741423054503100142560ustar00rootroot00000000000000#! /usr/bin/env python3 import sys from setuptools import setup long_description = ''' *doit* comes from the idea of bringing the power of build-tools to execute any kind of task *doit* can be uses as a simple **Task Runner** allowing you to easily define ad hoc tasks, helping you to organize all your project related tasks in an unified easy-to-use & discoverable way. *doit* scales-up with an efficient execution model like a **build-tool**. *doit* creates a DAG (direct acyclic graph) and is able to cache task results. It ensures that only required tasks will be executed and in the correct order (aka incremental-builds). The *up-to-date* check to cache task results is not restricted to looking for file modification on dependencies. Nor it requires "target" files. So it is also suitable to handle **workflows** not handled by traditional build-tools. Tasks' dependencies and creation can be done dynamically during it is execution making it suitable to drive complex workflows and **pipelines**. *doit* is build with a plugin architecture allowing extensible commands, custom output, storage backend and "task loader". It also provides an API allowing users to create new applications/tools leveraging *doit* functionality like a framework. *doit* is a mature project being actively developed for more than 10 years. It includes several extras like: parallel execution, auto execution (watch for file changes), shell tab-completion, DAG visualisation, IPython integration, and more. Sample Code =========== Define functions returning python dict with task's meta-data. Snippet from `tutorial `_: .. code:: python def task_imports(): """find imports from a python module""" for name, module in PKG_MODULES.by_name.items(): yield { 'name': name, 'file_dep': [module.path], 'actions': [(get_imports, (PKG_MODULES, module.path))], } def task_dot(): """generate a graphviz's dot graph from module imports""" return { 'targets': ['requests.dot'], 'actions': [module_to_dot], 'getargs': {'imports': ('imports', 'modules')}, 'clean': True, } def task_draw(): """generate image from a dot file""" return { 'file_dep': ['requests.dot'], 'targets': ['requests.png'], 'actions': ['dot -Tpng %(dependencies)s -o %(targets)s'], 'clean': True, } Run from terminal:: $ doit list dot generate a graphviz's dot graph from module imports draw generate image from a dot file imports find imports from a python module $ doit . imports:requests.models . imports:requests.__init__ . imports:requests.help (...) . dot . draw Project Details =============== - Website & docs - `http://pydoit.org `_ - Project management on github - `https://github.com/pydoit/doit `_ - Discussion group - `https://groups.google.com/forum/#!forum/python-doit `_ - News/twitter - `https://twitter.com/pydoit `_ - Plugins, extensions and projects based on doit - `https://github.com/pydoit/doit/wiki/powered-by-doit `_ license ======= The MIT License Copyright (c) 2008-2022 Eduardo Naufel Schettino ''' setup(name = 'doit', description = 'doit - Automation Tool', version = '0.36.0', license = 'MIT', author = 'Eduardo Naufel Schettino', author_email = 'schettino72@gmail.com', url = 'http://pydoit.org', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Operating System :: POSIX', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: Science/Research', 'Intended Audience :: System Administrators', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Quality Assurance', 'Topic :: Scientific/Engineering', ], keywords = "build make task automation pipeline task-runner", project_urls = { 'Documentation': 'https://pydoit.org/', 'Source': 'https://github.com/pydoit/doit/', 'Tracker': 'https://github.com/pydoit/doit/issues', }, packages = ['doit'], python_requires='>=3.8', install_requires = ['cloudpickle', 'importlib-metadata>=4.4'], extras_require={ 'toml': ['tomli; python_version<"3.11"'] }, long_description = long_description, entry_points = { 'console_scripts': [ 'doit = doit.__main__:main' ] }, ) doit-0.36.0/tests/000077500000000000000000000000001423054503100136765ustar00rootroot00000000000000doit-0.36.0/tests/Dockerfile000066400000000000000000000006721423054503100156750ustar00rootroot00000000000000# Run/test doit on debian unstable # docker build -t doit-debian . # docker run -it --cap-add SYS_PTRACE -v /home/eduardo/work/doit/dev:/root/doit doit-debian # pip3 install -e . # pip3 install -r dev_requirements.txt from debian:unstable RUN apt-get update && apt-get install eatmydata --no-install-recommends -y RUN eatmydata apt-get install python3-pytest python3-pip -y RUN apt-get install python3-gdbm strace -y WORKDIR /root/doit doit-0.36.0/tests/__init__.py000066400000000000000000000000001423054503100157750ustar00rootroot00000000000000doit-0.36.0/tests/conftest.py000066400000000000000000000117221423054503100161000ustar00rootroot00000000000000import os import time from dbm import whichdb import pytest from doit.dependency import DbmDB, Dependency, MD5Checker from doit.task import Task from doit.cmd_base import get_loader def get_abspath(relativePath): """ return abs file path relative to this file""" return os.path.join(os.path.dirname(__file__), relativePath) # fixture to create a sample file to be used as file_dep def dependency_factory(relative_path): @pytest.fixture def dependency(request): path = get_abspath(relative_path) if os.path.exists(path): # pragma: no cover os.remove(path) ff = open(path, "w") ff.write("whatever" + str(time.asctime())) ff.close() def remove_dependency(): if os.path.exists(path): os.remove(path) request.addfinalizer(remove_dependency) return path return dependency dependency1 = dependency_factory("data/dependency1") dependency2 = dependency_factory("data/dependency2") # fixture to create a sample file to be used as file_dep @pytest.fixture def target1(request): path = get_abspath("data/target1") if os.path.exists(path): # pragma: no cover os.remove(path) def remove_path(): if os.path.exists(path): os.remove(path) request.addfinalizer(remove_path) return path # fixture for "doit.db". create/remove for every test def remove_db(filename): """remove db file from anydbm""" # dbm on some systems add '.db' on others add ('.dir', '.pag') extensions = [ '', #dbhash #gdbm '.bak', #dumbdb '.dat', #dumbdb '.dir', #dumbdb #dbm2 '.db', #dbm1 '.pag', #dbm2 ] for ext in extensions: if os.path.exists(filename + ext): os.remove(filename + ext) # dbm backends use different file extensions db_ext = { 'dbhash': [''], 'gdbm': [''], 'dbm': ['.db', '.dir'], 'dumbdbm': ['.dat'], # for python3 'dbm.ndbm': ['.db'], } def dep_manager_fixture(request, dep_class, tmp_path_factory): filename = str(tmp_path_factory.mktemp('x', True) / 'testdb') dep_file = Dependency(dep_class, filename) dep_file.whichdb = whichdb(dep_file.name) if dep_class is DbmDB else 'XXX' dep_file.name_ext = db_ext.get(dep_file.whichdb, ['']) def remove_depfile(): if not dep_file._closed: dep_file.close() remove_db(dep_file.name) request.addfinalizer(remove_depfile) return dep_file @pytest.fixture def dep_manager(request, tmp_path_factory): return dep_manager_fixture(request, DbmDB, tmp_path_factory) @pytest.fixture def depfile_name(request, tmp_path_factory): depfile_name = str(tmp_path_factory.mktemp('x', True) / 'testdb') def remove_depfile(): remove_db(depfile_name) request.addfinalizer(remove_depfile) return depfile_name @pytest.fixture def restore_cwd(request): """restore cwd to its initial value after test finishes.""" previous = os.getcwd() def restore_cwd(): os.chdir(previous) request.addfinalizer(restore_cwd) # create a list of sample tasks def tasks_sample(): tasks_sample = [ # 0 Task( "t1", [""], doc="t1 doc string", params=[ { 'name': 'arg1', 'short': 'a', 'long': 'arg1', 'default': 'default_value', }, ]), # 1 Task("t2", [""], file_dep=['tests/data/dependency1'], doc="t2 doc string"), # 2 Task("g1", None, doc="g1 doc string", has_subtask=True), # 3 Task("g1.a", [""], doc="g1.a doc string", subtask_of='g1'), # 4 Task("g1.b", [""], doc="g1.b doc string", subtask_of='g1'), # 5 Task("t3", [""], doc="t3 doc string", task_dep=["t1"]) ] tasks_sample[2].task_dep = ['g1.a', 'g1.b'] return tasks_sample def tasks_bad_sample(): """Create list of tasks that cause errors.""" bad_sample = [ Task("e1", [""], doc='e4 bad file dep', file_dep=['xxxx']) ] return bad_sample def CmdFactory(cls, outstream=None, task_loader=None, dep_file=None, backend=None, task_list=None, sel_tasks=None, sel_default_tasks=False, dep_manager=None, config=None, cmds=None): """helper for test code, so test can call _execute() directly""" loader = get_loader(config, task_loader, cmds) cmd = cls(task_loader=loader, config=config, cmds=cmds) if outstream: cmd.outstream = outstream if backend: assert backend == "dbm" # the only one used on tests cmd.dep_manager = Dependency(DbmDB, dep_file, MD5Checker) elif dep_manager: cmd.dep_manager = dep_manager cmd.dep_file = dep_file # (str) filename usually '.doit.db' cmd.task_list = task_list # list of tasks cmd.sel_tasks = sel_tasks # from command line or default_tasks cmd.sel_default_tasks = sel_default_tasks return cmd doit-0.36.0/tests/data/000077500000000000000000000000001423054503100146075ustar00rootroot00000000000000doit-0.36.0/tests/data/README000066400000000000000000000001001423054503100154560ustar00rootroot00000000000000this folder is used to keep some temporary files used on tests. doit-0.36.0/tests/loader_sample.py000066400000000000000000000004301423054503100170540ustar00rootroot00000000000000 DOIT_CONFIG = {'verbose': 2} def task_xxx1(): """task doc""" return { 'actions': ['do nothing'], 'params': [{'name':'p1', 'default':'1', 'short':'p'}], } def task_yyy2(): return {'actions':None} def bad_seed(): # pragma: no cover pass doit-0.36.0/tests/module_with_tasks.py000066400000000000000000000003351423054503100177760ustar00rootroot00000000000000"""ModuleLoadTest uses this file to load tasks from module.""" DOIT_CONFIG = dict(verbose=2) def task_xxx1(): return dict(actions=[]) task_no = 'strings are not tasks' def blabla(): ... # pragma: no cover doit-0.36.0/tests/myecho.py000066400000000000000000000003221423054503100155310ustar00rootroot00000000000000#! /usr/bin/env python3 # tests on CmdTask will use this script as an external process. # just print out all arguments import sys if __name__ == "__main__": print(" ".join(sys.argv[1:])) sys.exit(0) doit-0.36.0/tests/pyproject.toml000066400000000000000000000001411423054503100166060ustar00rootroot00000000000000[tool.doit] optx = "2" opty = "3" [tool.doit.plugins.command] bar = "tests.sample_plugin:MyCmd" doit-0.36.0/tests/sample.cfg000066400000000000000000000001071423054503100156360ustar00rootroot00000000000000[GLOBAL] optx = 6 opty = 7 [COMMAND] foo = tests.sample_plugin:MyCmd doit-0.36.0/tests/sample.toml000066400000000000000000000001761423054503100160600ustar00rootroot00000000000000optx = "6" opty = "7" [plugins.command] foo = "tests.sample_plugin:MyCmd" [commands.foo] nval = 33 [tasks.bar] opt = "baz" doit-0.36.0/tests/sample_md5.txt000066400000000000000000000036571423054503100165000ustar00rootroot00000000000000MD5SUM(1) User Commands MD5SUM(1) NAME md5sum - compute and check MD5 message digest SYNOPSIS md5sum [OPTION] [FILE]... DESCRIPTION Print or check MD5 (128-bit) checksums. With no FILE, or when FILE is -, read standard input. -b, --binary read in binary mode -c, --check read MD5 sums from the FILEs and check them -t, --text read in text mode (default) The following two options are useful only when verifying checksums: --status don’t output anything, status code shows success -w, --warn warn about improperly formatted checksum lines --help display this help and exit --version output version information and exit The sums are computed as described in RFC 1321. When checking, the input should be a former output of this program. The default mode is to print a line with checksum, a character indicating type (‘*’ for binary, ‘ ’ for text), and name for each FILE. AUTHOR Written by Ulrich Drepper, Scott Miller, and David Madore. REPORTING BUGS Report bugs to . COPYRIGHT Copyright © 2006 Free Software Foundation, Inc. This is free software. You may redistribute copies of it under the terms of the GNU General Public License . There is NO WARRANTY, to the extent permitted by law. SEE ALSO The full documentation for md5sum is maintained as a Texinfo manual. If the info and md5sum programs are properly installed at your site, the command info md5sum should give you access to the complete manual. md5sum 5.97 September 2007 MD5SUM(1) doit-0.36.0/tests/sample_plugin.py000066400000000000000000000013011423054503100171020ustar00rootroot00000000000000from doit.cmd_base import Command class MyCmd(Command): name = 'mycmd' doc_purpose = 'test extending doit commands' doc_usage = '[XXX]' doc_description = 'my command description' def execute(self, opt_values, pos_args): # pragma: no cover print("this command does nothing!") ############## from doit.task import dict_to_task from doit.cmd_base import TaskLoader2 my_builtin_task = { 'name': 'sample_task', 'actions': ['echo hello from built in'], 'doc': 'sample doc', } class MyLoader(TaskLoader2): def load_doit_config(self): return {'verbosity': 2} def load_tasks(self, cmd, pos_args): return [dict_to_task(my_builtin_task)] doit-0.36.0/tests/sample_process.py000066400000000000000000000023701423054503100172710ustar00rootroot00000000000000#! /usr/bin/env python3 # tests on CmdTask will use this script as an external process. # - If you call this script with 3 or more arguments, the process returns # exit code (166). # - If you call this script with arguments "please fail", it returns exit # code (11). # - If you call this script with arguments "check env", it verifies the # existence of an environment variable called "GELKIPWDUZLOVSXE", with # value "1". If the variable is not found, the process returns exit code (99). # - Otherwise, any first argument gets written to STDOUT. Any second argument # gets written to STDERR. import os import sys if __name__ == "__main__": # error if len(sys.argv) > 3: sys.exit(166) # fail if len(sys.argv) == 3 and sys.argv[1]=='please' and sys.argv[2]=='fail': sys.stdout.write("out ouch") sys.stderr.write("err output on failure") sys.exit(11) # check env if len(sys.argv) == 3 and sys.argv[1]=='check' and sys.argv[2]=='env': if os.environ.get('GELKIPWDUZLOVSXE') == '1': sys.exit(0) else: sys.exit(99) # ok if len(sys.argv) > 1: sys.stdout.write(sys.argv[1]) if len(sys.argv) > 2: sys.stderr.write(sys.argv[2]) sys.exit(0) doit-0.36.0/tests/test___init__.py000066400000000000000000000007261423054503100170530ustar00rootroot00000000000000import os import doit from doit.loader import get_module def test_get_initial_workdir(restore_cwd): initial_wd = os.getcwd() fileName = os.path.join(os.path.dirname(__file__),"loader_sample.py") cwd = os.path.normpath(os.path.join(os.path.dirname(__file__), "data")) assert cwd != initial_wd # make sure test is not too easy get_module(fileName, cwd) assert os.getcwd() == cwd, os.getcwd() assert doit.get_initial_workdir() == initial_wd doit-0.36.0/tests/test___main__.py000066400000000000000000000003171423054503100170300ustar00rootroot00000000000000import subprocess from sys import executable def test_execute(depfile_name): assert 0 == subprocess.call([executable, '-m', 'doit', 'list', '--db-file', depfile_name]) doit-0.36.0/tests/test_action.py000066400000000000000000001004001423054503100165570ustar00rootroot00000000000000import os import sys import io import tempfile import textwrap import locale locale # quiet pyflakes from pathlib import PurePath, Path from io import StringIO, BytesIO from threading import Thread import time from sys import executable from unittest.mock import Mock import pytest from doit import action from doit.task import Task from doit.exceptions import TaskError, TaskFailed #path to test folder TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) @pytest.fixture def tmpfile(request): temp = tempfile.TemporaryFile('w+', encoding="utf-8") request.addfinalizer(temp.close) return temp ############# CmdAction class TestCmdAction(object): # if nothing is raised it is successful def test_success(self): my_action = action.CmdAction(PROGRAM) got = my_action.execute() assert got is None def test_success_noshell(self): my_action = action.CmdAction(PROGRAM.split(), shell=False) got = my_action.execute() assert got is None def test_error(self): my_action = action.CmdAction("%s 1 2 3" % PROGRAM) got = my_action.execute() assert isinstance(got, TaskError) def test_env(self): env = os.environ.copy() env['GELKIPWDUZLOVSXE'] = '1' my_action = action.CmdAction("%s check env" % PROGRAM, env=env) got = my_action.execute() assert got is None def test_failure(self): my_action = action.CmdAction("%s please fail" % PROGRAM) got = my_action.execute() assert isinstance(got, TaskFailed) def test_str(self): my_action = action.CmdAction(PROGRAM) assert "Cmd: %s" % PROGRAM == str(my_action) def test_unicode(self): action_str = PROGRAM + "中文" my_action = action.CmdAction(action_str) assert "Cmd: %s" % action_str == str(my_action) def test_repr(self): my_action = action.CmdAction(PROGRAM) expected = "" % PROGRAM assert expected == repr(my_action), repr(my_action) def test_result(self): my_action = action.CmdAction("%s 1 2" % PROGRAM) my_action.execute() assert "12" == my_action.result def test_values(self): # for cmdActions they are empty if save_out not specified my_action = action.CmdAction("%s 1 2" % PROGRAM) my_action.execute() assert {} == my_action.values class TestCmdActionParams(object): def test_invalid_param_stdout(self): pytest.raises(action.InvalidTask, action.CmdAction, [PROGRAM], stdout=None) def test_changePath(self, tmpdir): path = tmpdir.mkdir("foo") command = '%s -c "import os; print(os.getcwd())"' % executable my_action = action.CmdAction(command, cwd=path.strpath) my_action.execute() assert path + os.linesep == my_action.out, repr(my_action.out) def test_noPathSet(self, tmpdir): path = tmpdir.mkdir("foo") command = '%s -c "import os; print(os.getcwd())"' % executable my_action = action.CmdAction(command) my_action.execute() assert path.strpath + os.linesep != my_action.out, repr(my_action.out) class TestCmdVerbosity(object): # Capture stderr def test_captureStderr(self): cmd = "%s please fail" % PROGRAM my_action = action.CmdAction(cmd) got = my_action.execute() assert isinstance(got, TaskFailed) assert "err output on failure" == my_action.err, repr(my_action.err) # Capture stdout def test_captureStdout(self): my_action = action.CmdAction("%s hi_stdout hi2" % PROGRAM) my_action.execute() assert "hi_stdout" == my_action.out, repr(my_action.out) # Do not capture stderr # test using a tempfile. it is not possible (at least i dont know) # how to test if the output went to the parent process, # faking sys.stderr with a StringIO doesnt work. def test_noCaptureStderr(self, tmpfile): my_action = action.CmdAction("%s please fail" % PROGRAM) action_result = my_action.execute(err=tmpfile) assert isinstance(action_result, TaskFailed) tmpfile.seek(0) got = tmpfile.read() assert "err output on failure" == got, repr(got) assert "err output on failure" == my_action.err, repr(my_action.err) # Do not capture stdout def test_noCaptureStdout(self, tmpfile): my_action = action.CmdAction("%s hi_stdout hi2" % PROGRAM) my_action.execute(out=tmpfile) tmpfile.seek(0) got = tmpfile.read() assert "hi_stdout" == got, repr(got) assert "hi_stdout" == my_action.out, repr(my_action.out) class TestTaskIOCapture: def test_cmd_io_capture_yes(self): task = Task(name='foo', actions=[f"{PROGRAM} hi_stdout hi2"], io={'capture': True}) task.init_options() my_action = task.actions[0] got = my_action.execute() assert got is None assert "hi_stdout" == my_action.out def test_cmd_io_capture_no(self): task = Task(name='foo', actions=[f"{PROGRAM} hi_stdout hi2"], io={'capture': False}) task.init_options() my_action = task.actions[0] got = my_action.execute() assert got is None assert my_action.out is None def test_py_io_capture_yes(self): def hello(): print('hello') task = Task(name='foo', actions=[hello], io={'capture': True}) task.init_options() my_action = task.actions[0] got = my_action.execute() assert got is None assert "hello\n" == my_action.out def test_py_io_capture_no(self): def hello(): print('hello') task = Task(name='foo', actions=[hello], io={'capture': False}) task.init_options() my_action = task.actions[0] got = my_action.execute() assert got is None assert my_action.out is None class TestCmdExpandAction(object): def test_task_meta_reference(self): cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " %(dependencies)s - %(changed)s - %(targets)s" dependencies = ["data/dependency1", "data/dependency2"] targets = ["data/target", "data/targetXXX"] task = Task('Fake', [cmd], dependencies, targets) task.dep_changed = ["data/dependency1"] task.options = {} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.split('-') assert task.file_dep == set(got[0].split()) assert task.dep_changed == got[1].split() assert targets == got[2].split() def test_task_options(self): cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " %(opt1)s - %(opt2)s" task = Task('Fake', [cmd]) task.options = {'opt1':'3', 'opt2':'abc def'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "3 - abc def" == got def test_task_pos_arg(self): cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " %(pos)s" task = Task('Fake', [cmd], pos_arg='pos') task.options = {} task.pos_arg_val = ['hi', 'there'] my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "hi there" == got def test_task_pos_arg_None(self): # pos_arg_val is None when the task is not specified from # command line but executed because it is a task_dep cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " %(pos)s" task = Task('Fake', [cmd], pos_arg='pos') task.options = {} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "" == got def test_callable_return_command_str(self): def get_cmd(opt1, opt2): cmd = "%s %s/myecho.py" % (executable, TEST_PATH) return cmd + " %s - %s" % (opt1, opt2) task = Task('Fake', [action.CmdAction(get_cmd)]) task.options = {'opt1':'3', 'opt2':'abc def'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "3 - abc def" == got, repr(got) def test_callable_tuple_return_command_str(self): def get_cmd(opt1, opt2): cmd = "%s %s/myecho.py" % (executable, TEST_PATH) return cmd + " %s - %s" % (opt1, opt2) task = Task('Fake', [action.CmdAction((get_cmd, [], {'opt2':'abc def'}))]) task.options = {'opt1':'3'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "3 - abc def" == got, repr(got) def test_callable_invalid(self): def get_cmd(blabla): pass task = Task('Fake', [action.CmdAction(get_cmd)]) task.options = {'opt1':'3'} my_action = task.actions[0] got = my_action.execute() assert isinstance(got, TaskError) def test_string_list_cant_be_expanded(self): cmd = [executable, "%s/myecho.py" % TEST_PATH] task = Task('Fake', [cmd]) my_action = task.actions[0] assert cmd == my_action.expand_action() def test_list_can_contain_path(self): cmd = [executable, PurePath(TEST_PATH), Path("myecho.py")] task = Task('Fake', [cmd]) my_action = task.actions[0] assert [executable, TEST_PATH, "myecho.py"] == my_action.expand_action() def test_list_should_contain_strings_or_paths(self): cmd = [executable, PurePath(TEST_PATH), 42, Path("myecho.py")] task = Task('Fake', [cmd]) my_action = task.actions[0] assert pytest.raises(action.InvalidTask, my_action.expand_action) class TestCmdActionStringFormatting(object): def test_old(self, monkeypatch): monkeypatch.setattr(action.CmdAction, 'STRING_FORMAT', 'old') cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " %(dependencies)s - %(opt1)s" task = Task('Fake', [cmd], ['data/dependency1']) task.options = {'opt1':'abc'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "data/dependency1 - abc" == got def test_new(self, monkeypatch): monkeypatch.setattr(action.CmdAction, 'STRING_FORMAT', 'new') cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " {dependencies} - {opt1}" task = Task('Fake', [cmd], ['data/dependency1']) task.options = {'opt1':'abc'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "data/dependency1 - abc" == got def test_both(self, monkeypatch): monkeypatch.setattr(action.CmdAction, 'STRING_FORMAT', 'both') cmd = "%s %s/myecho.py" % (executable, TEST_PATH) cmd += " {dependencies} - %(opt1)s" task = Task('Fake', [cmd], ['data/dependency1']) task.options = {'opt1':'abc'} my_action = task.actions[0] assert my_action.execute() is None got = my_action.out.strip() assert "data/dependency1 - abc" == got class TestCmd_print_process_output_line(object): def test_non_unicode_string_error_strict(self): my_action = action.CmdAction("", decode_error='strict') not_unicode = BytesIO('\xa9'.encode("latin-1")) realtime = Mock() realtime.encoding = 'utf-8' pytest.raises(UnicodeDecodeError, my_action._print_process_output, Mock(), not_unicode, Mock(), realtime) def test_non_unicode_string_error_replace(self): my_action = action.CmdAction("") # default is decode_error = 'replace' not_unicode = BytesIO('\xa9'.encode("latin-1")) realtime = Mock() realtime.encoding = 'utf-8' capture = StringIO() my_action._print_process_output( Mock(), not_unicode, capture, realtime) # get the replacement char expected = '�' assert expected == capture.getvalue() def test_non_unicode_string_ok(self): my_action = action.CmdAction("", encoding='iso-8859-1') not_unicode = BytesIO('\xa9'.encode("latin-1")) realtime = Mock() realtime.encoding = 'utf-8' capture = StringIO() my_action._print_process_output( Mock(), not_unicode, capture, realtime) # get the correct char from latin-1 encoding expected = '©' assert expected == capture.getvalue() # dont test unicode if system locale doesnt support unicode # see https://bitbucket.org/schettino72/doit/pull-request/11 @pytest.mark.skipif('locale.getlocale()[1] is None') def test_unicode_string(self, tmpfile): my_action = action.CmdAction("") unicode_in = tempfile.TemporaryFile('w+b') unicode_in.write(" 中文".encode('utf-8')) unicode_in.seek(0) my_action._print_process_output( Mock(), unicode_in, Mock(), tmpfile) @pytest.mark.skipif('locale.getlocale()[1] is None') def test_unicode_string2(self, tmpfile): # this \uXXXX has a different behavior! my_action = action.CmdAction("") unicode_in = tempfile.TemporaryFile('w+b') unicode_in.write(" 中文 \u2018".encode('utf-8')) unicode_in.seek(0) my_action._print_process_output( Mock(), unicode_in, Mock(), tmpfile) def test_line_buffered_output(self): my_action = action.CmdAction("") out, inp = os.pipe() out, inp = os.fdopen(out, 'rb'), os.fdopen(inp, 'wb') inp.write('abcd\nline2'.encode('utf-8')) inp.flush() capture = StringIO() thread = Thread(target=my_action._print_process_output, args=(Mock(), out, capture, None)) thread.start() time.sleep(0.1) try: got = capture.getvalue() # 'line2' is not captured because of line buffering assert 'abcd\n' == got print('asserted') finally: inp.close() def test_unbuffered_output(self): my_action = action.CmdAction("", buffering=1) out, inp = os.pipe() out, inp = os.fdopen(out, 'rb'), os.fdopen(inp, 'wb') inp.write('abcd\nline2'.encode('utf-8')) inp.flush() capture = StringIO() thread = Thread(target=my_action._print_process_output, args=(Mock(), out, capture, None)) thread.start() time.sleep(0.1) try: got = capture.getvalue() assert 'abcd\nline2' == got finally: inp.close() def test_unbuffered_env(self, monkeypatch): my_action = action.CmdAction("", buffering=1) proc_mock = Mock() proc_mock.configure_mock(returncode=0) popen_mock = Mock(return_value=proc_mock) from doit.action import subprocess monkeypatch.setattr(subprocess, 'Popen', popen_mock) my_action._print_process_output = Mock() my_action.execute() env = popen_mock.call_args[-1]['env'] assert env and env.get('PYTHONUNBUFFERED', False) == '1' class TestCmdSaveOuput(object): def test_success(self): TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) my_action = action.CmdAction(PROGRAM + " x1 x2", save_out='out') my_action.execute() assert {'out': 'x1'} == my_action.values class FakeStream(): def __init__(self, tty, fileno=None): self.tty = tty self._fileno = fileno def isatty(self): return self.tty def fileno(self): return self._fileno class TestWriter(object): def test_write(self): w1 = StringIO() w2 = StringIO() writer = action.Writer(w1, w2) writer.flush() # make sure flush is supported writer.write("hello") assert "hello" == w1.getvalue() assert "hello" == w2.getvalue() def test_isatty_true(self): w1 = StringIO() writer = action.Writer(w1) w2 = FakeStream(True) writer.add_writer(w2, is_original=True) assert writer.isatty() def test_isatty_false(self): # not a tty even if stream is a tty but not marked as original stream w1 = FakeStream(True) w1.isatty = lambda: True writer = action.Writer(w1) assert not writer.isatty() def test_fileno(self): w1 = StringIO() w2 = FakeStream(True, 32) writer = action.Writer() writer.add_writer(w1) writer.add_writer(w2, is_original=True) assert writer.isatty() assert 32 == writer.fileno() def test_fileno_not_supported(self): w1 = FakeStream(True, 11) writer = action.Writer(w1) pytest.raises(io.UnsupportedOperation, writer.fileno) ############# PythonAction class TestPythonAction(object): def test_success_bool(self): def success_sample():return True my_action = action.PythonAction(success_sample) # nothing raised it was successful my_action.execute() def test_success_None(self): def success_sample():return my_action = action.PythonAction(success_sample) # nothing raised it was successful my_action.execute() def test_success_str(self): def success_sample():return "" my_action = action.PythonAction(success_sample) # nothing raised it was successful my_action.execute() def test_success_dict(self): def success_sample():return {} my_action = action.PythonAction(success_sample) # nothing raised it was successful my_action.execute() def test_error_object(self): # anthing but None, bool, string or dict def error_sample(): return object() my_action = action.PythonAction(error_sample) got = my_action.execute() assert isinstance(got, TaskError) def test_error_taskfail(self): # should get the same exception as was returned from the # user's function def error_sample(): return TaskFailed("too bad") ye_olde_action = action.PythonAction(error_sample) ret = ye_olde_action.execute() assert isinstance(ret, TaskFailed) assert str(ret).endswith("too bad\n") def test_error_taskerror(self): def error_sample(): return TaskError("so sad") ye_olde_action = action.PythonAction(error_sample) ret = ye_olde_action.execute() assert str(ret).endswith("so sad\n") def test_error_exception(self): def error_sample(): raise Exception("asdf") my_action = action.PythonAction(error_sample) got = my_action.execute() assert isinstance(got, TaskError) def test_fail_bool(self): def fail_sample():return False my_action = action.PythonAction(fail_sample) got = my_action.execute() assert isinstance(got, TaskFailed) # any callable should work, not only functions def test_callable_obj(self): class CallMe: def __call__(self): return False my_action = action.PythonAction(CallMe()) got = my_action.execute() assert isinstance(got, TaskFailed) # helper to test callable with parameters def _func_par(self,par1,par2,par3=5): if par1 == par2 and par3 > 10: return True else: return False def test_init(self): # default values action1 = action.PythonAction(self._func_par) assert action1.args == [] assert action1.kwargs == {} # not a callable pytest.raises(action.InvalidTask, action.PythonAction, "abc") # args not a list pytest.raises(action.InvalidTask, action.PythonAction, self._func_par, "c") # kwargs not a list pytest.raises(action.InvalidTask, action.PythonAction, self._func_par, None, "a") # cant use a class as callable def test_init_callable_class(self): class CallMe(object): pass pytest.raises(action.InvalidTask, action.PythonAction, CallMe) # cant use built-ins def test_init_callable_builtin(self): pytest.raises(action.InvalidTask, action.PythonAction, any) def test_functionParametersArgs(self): my_action = action.PythonAction(self._func_par,args=(2,2,25)) my_action.execute() def test_functionParametersKwargs(self): my_action = action.PythonAction(self._func_par, kwargs={'par1':2,'par2':2,'par3':25}) my_action.execute() def test_functionParameters(self): my_action = action.PythonAction(self._func_par,args=(2,2), kwargs={'par3':25}) my_action.execute() def test_functionParametersFail(self): my_action = action.PythonAction(self._func_par, args=(2,3), kwargs={'par3':25}) got = my_action.execute() assert isinstance(got, TaskFailed) def test_str(self): def str_sample(): return True my_action = action.PythonAction(str_sample) assert "Python: function" in str(my_action) assert "str_sample" in str(my_action) def test_repr(self): def repr_sample(): return True my_action = action.PythonAction(repr_sample) assert "" % repr(repr_sample) == repr(my_action) def test_result(self): def vvv(): return "my value" my_action = action.PythonAction(vvv) my_action.execute() assert "my value" == my_action.result def test_result_dict(self): def vvv(): return {'xxx': "my value"} my_action = action.PythonAction(vvv) my_action.execute() assert {'xxx': "my value"} == my_action.result def test_values(self): def vvv(): return {'x': 5, 'y':10} my_action = action.PythonAction(vvv) my_action.execute() assert {'x': 5, 'y':10} == my_action.values class TestPythonVerbosity(object): def write_stderr(self): sys.stderr.write("this is stderr S\n") def write_stdout(self): sys.stdout.write("this is stdout S\n") def test_captureStderr(self): my_action = action.PythonAction(self.write_stderr) my_action.execute() assert "this is stderr S\n" == my_action.err, repr(my_action.err) def test_captureStdout(self): my_action = action.PythonAction(self.write_stdout) my_action.execute() assert "this is stdout S\n" == my_action.out, repr(my_action.out) def test_noCaptureStderr(self, capsys): my_action = action.PythonAction(self.write_stderr) my_action.execute(err=sys.stderr) got = capsys.readouterr()[1] assert "this is stderr S\n" == got, repr(got) def test_noCaptureStdout(self, capsys): my_action = action.PythonAction(self.write_stdout) my_action.execute(out=sys.stdout) got = capsys.readouterr()[0] assert "this is stdout S\n" == got, repr(got) def test_redirectStderr(self): tmpfile = tempfile.TemporaryFile('w+') my_action = action.PythonAction(self.write_stderr) my_action.execute(err=tmpfile) tmpfile.seek(0) got = tmpfile.read() tmpfile.close() assert "this is stderr S\n" == got, got def test_redirectStdout(self): tmpfile = tempfile.TemporaryFile('w+') my_action = action.PythonAction(self.write_stdout) my_action.execute(out=tmpfile) tmpfile.seek(0) got = tmpfile.read() tmpfile.close() assert "this is stdout S\n" == got, got class TestPythonActionPrepareKwargsMeta(object): def test_no_extra_args(self): # no error trying to inject values def py_callable(): return True task = Task('Fake', [py_callable], file_dep=['dependencies']) task.options = {} my_action = task.actions[0] my_action.execute() def test_keyword_extra_args(self): got = [] def py_callable(arg=None, **kwargs): got.append(kwargs) my_task = Task('Fake', [(py_callable, (), {'b': 4})], file_dep=['dependencies']) my_task.options = {'foo': 'bar'} my_action = my_task.actions[0] my_action.execute() # meta args do not leak into kwargs assert got == [{'foo': 'bar', 'b': 4}] def test_named_extra_args(self): got = [] def py_callable(targets, dependencies, changed, task): got.append(targets) got.append(dependencies) got.append(changed) got.append(task) task = Task('Fake', [py_callable], file_dep=['dependencies'], targets=['targets']) task.dep_changed = ['changed'] task.options = {} my_action = task.actions[0] my_action.execute() assert got == [['targets'], ['dependencies'], ['changed'], task] def test_mixed_args(self): got = [] def py_callable(a, b, changed): got.append(a) got.append(b) got.append(changed) task = Task('Fake', [(py_callable, ('a', 'b'))]) task.options = {} task.dep_changed = ['changed'] my_action = task.actions[0] my_action.execute() assert got == ['a', 'b', ['changed']] def test_extra_arg_overwritten(self): got = [] def py_callable(a, b, changed): got.append(a) got.append(b) got.append(changed) task = Task('Fake', [(py_callable, ('a', 'b', 'c'))]) task.dep_changed = ['changed'] task.options = {} my_action = task.actions[0] my_action.execute() assert got == ['a', 'b', 'c'] def test_extra_kwarg_overwritten(self): got = [] def py_callable(a, b, **kwargs): got.append(a) got.append(b) got.append(kwargs['changed']) task = Task('Fake', [(py_callable, ('a', 'b'), {'changed': 'c'})]) task.options = {} task.dep_changed = ['changed'] my_action = task.actions[0] my_action.execute() assert got == ['a', 'b', 'c'] def test_meta_arg_default_disallowed(self): def py_callable(a, b, changed=None): pass task = Task('Fake', [(py_callable, ('a', 'b'))]) task.options = {} task.dep_changed = ['changed'] my_action = task.actions[0] pytest.raises(action.InvalidTask, my_action.execute) def test_callable_obj(self): got = [] class CallMe(object): def __call__(self, a, b, changed): got.append(a) got.append(b) got.append(changed) task = Task('Fake', [(CallMe(), ('a', 'b'))]) task.options = {} task.dep_changed = ['changed'] my_action = task.actions[0] my_action.execute() assert got == ['a', 'b', ['changed']] def test_method(self): got = [] class CallMe(object): def xxx(self, a, b, changed): got.append(a) got.append(b) got.append(changed) task = Task('Fake', [(CallMe().xxx, ('a', 'b'))]) task.options = {} task.dep_changed = ['changed'] my_action = task.actions[0] my_action.execute() assert got == ['a', 'b', ['changed']] def test_task_options(self): got = [] def py_callable(opt1, opt3): got.append(opt1) got.append(opt3) task = Task('Fake', [py_callable]) task.options = {'opt1':'1', 'opt2':'abc def', 'opt3':3} my_action = task.actions[0] my_action.execute() assert ['1',3] == got, repr(got) def test_task_pos_arg(self): got = [] def py_callable(pos): got.append(pos) task = Task('Fake', [py_callable], pos_arg='pos') task.options = {} task.pos_arg_val = ['hi', 'there'] my_action = task.actions[0] my_action.execute() assert [['hi', 'there']] == got, repr(got) def test_option_default_allowed(self): got = [] def py_callable(opt2='ABC'): got.append(opt2) task = Task('Fake', [py_callable]) task.options = {'opt2':'123'} my_action = task.actions[0] my_action.execute() assert ['123'] == got, repr(got) def test_kwonlyargs_minimal(self): got = [] scope = {'got': got} exec(textwrap.dedent(''' def py_callable(*args, kwonly=None): got.append(args) got.append(kwonly) '''), scope) task = Task('Fake', [(scope['py_callable'], (1, 2, 3), {'kwonly': 4})]) task.options = {} my_action = task.actions[0] my_action.execute() assert [(1, 2, 3), 4] == got, repr(got) def test_kwonlyargs_full(self): got = [] scope = {'got': got} exec(textwrap.dedent(''' def py_callable(pos, *args, kwonly=None, **kwargs): got.append(pos) got.append(args) got.append(kwonly) got.append(kwargs['foo']) '''), scope) task = Task('Fake', [ (scope['py_callable'], [1,2,3], {'kwonly': 4, 'foo': 5})]) task.options = {} my_action = task.actions[0] my_action.execute() assert [1, (2, 3), 4, 5] == got, repr(got) def test_action_modifies_task_but_not_attrs(self): def py_callable(targets, dependencies, changed, task): targets.append('new_target') dependencies.append('new_dependency') changed.append('new_changed') task.file_dep.add('dep2') my_task = Task('Fake', [py_callable], file_dep=['dependencies'], targets=['targets']) my_task.dep_changed = ['changed'] my_task.options = {} my_action = my_task.actions[0] my_action.execute() assert my_task.file_dep == set(['dependencies', 'dep2']) assert my_task.targets == ['targets'] assert my_task.dep_changed == ['changed'] ############## class TestCreateAction(object): class TaskStub(object): name = 'stub' mytask = TaskStub() def testBaseAction(self): class Sample(action.BaseAction): pass my_action = action.create_action(Sample(), self.mytask, 'actions') assert isinstance(my_action, Sample) assert self.mytask == my_action.task def testStringAction(self): my_action = action.create_action("xpto 14 7", self.mytask, 'actions') assert isinstance(my_action, action.CmdAction) assert my_action.shell == True def testListStringAction(self): my_action = action.create_action(["xpto", 14, 7], self.mytask, 'actions') assert isinstance(my_action, action.CmdAction) assert my_action.shell == False def testMethodAction(self): def dumb(): return my_action = action.create_action(dumb, self.mytask, 'actions') assert isinstance(my_action, action.PythonAction) def testTupleAction(self): def dumb(): return my_action = action.create_action((dumb,[1,2],{'a':5}), self.mytask, 'actions') assert isinstance(my_action, action.PythonAction) def testTupleActionMoreThanThreeElements(self): def dumb(): return expected = "Task 'stub': invalid 'actions' tuple length" with pytest.raises(action.InvalidTask, match=expected): action.create_action((dumb,[1,2],{'a':5},'oo'), self.mytask, 'actions') def testInvalidActionNone(self): expected = "Task 'stub': invalid 'actions' type. got: None" with pytest.raises(action.InvalidTask, match=expected): action.create_action(None, self.mytask, 'actions') def testInvalidActionObject(self): expected = "Task 'stub': invalid 'actions' type. got: <" with pytest.raises(action.InvalidTask, match=expected): action.create_action(self, self.mytask, 'actions') def test_invalid_action_task_param_name(self): expected = "Task 'stub': invalid 'clean' type. got: True" with pytest.raises(action.InvalidTask, match=expected): action.create_action(True, self.mytask, 'clean') doit-0.36.0/tests/test_api.py000066400000000000000000000045041423054503100160630ustar00rootroot00000000000000import sys from doit.cmd_base import ModuleTaskLoader from doit.api import run, run_tasks def test_run(monkeypatch, depfile_name): monkeypatch.setattr(sys, 'argv', ['did', '--db-file', depfile_name]) try: def hi(): print('hi') def task_hi(): return {'actions': [hi]} run(locals()) except SystemExit as err: assert err.code == 0 else: # pragma: no cover assert False def _dodo(): """sample tasks""" def hi(opt=None): print('hi', opt) def task_hi(): return { 'actions': [hi], 'params': [{'name': 'opt', 'default': '1'}], } def task_two(): def my_error(): return False return { 'actions': [my_error], } return { 'task_hi': task_hi, 'task_two': task_two, } def test_run_tasks_success(capsys, depfile_name): result = run_tasks( ModuleTaskLoader(_dodo()), {'hi': {'opt': '3'}}, extra_config = { 'GLOBAL': { 'verbosity': 2, 'dep_file': depfile_name, }, }, ) assert result == 0 out = capsys.readouterr().out assert out.strip() == 'hi 3' def test_run_tasks_error(capsys, depfile_name): result = run_tasks( ModuleTaskLoader(_dodo()), {'two': None}, extra_config = { 'GLOBAL': { 'verbosity': 2, 'dep_file': depfile_name, }, }, ) assert result == 1 def test_run_tasks_pos(capsys, depfile_name): def _dodo_pos(): """sample tasks""" def hi(opt, pos): print(f'hi:{opt}--{pos}') def task_hi(): return { 'actions': [hi], 'params': [{'name': 'opt', 'default': '1'}], 'pos_arg': 'pos', } return { 'task_hi': task_hi, } tasks_selection = {'hi': {'opt': '3', 'pos': 'foo bar baz'}} extra_config = { 'GLOBAL': { 'verbosity': 2, 'dep_file': depfile_name, }, } result = run_tasks(ModuleTaskLoader(_dodo_pos()), tasks_selection, extra_config=extra_config) assert result == 0 out = capsys.readouterr().out assert out.strip() == 'hi:3--foo bar baz' doit-0.36.0/tests/test_cmd_base.py000066400000000000000000000345431423054503100170550ustar00rootroot00000000000000import os from unittest import mock import pytest from doit import version from doit.cmdparse import CmdParseError, CmdParse from doit.exceptions import InvalidCommand, InvalidDodoFile from doit.dependency import FileChangedChecker, JSONCodec from doit.task import Task from doit.loader import task_params from doit.cmd_base import version_tuple, Command, DoitCmdBase from doit.cmd_base import get_loader, ModuleTaskLoader, DodoTaskLoader from doit.cmd_base import check_tasks_exist, tasks_and_deps_iter, subtasks_iter from .conftest import CmdFactory def test_version_tuple(): assert [1,2,3] == version_tuple([1,2,3]) assert [1,2,3] == version_tuple('1.2.3') assert [0,2,0] == version_tuple('0.2.0') assert [0,2,-1] == version_tuple('0.2.dev1') opt_bool = {'name': 'flag', 'short':'f', 'long': 'flag', 'inverse':'no-flag', 'type': bool, 'default': False, 'help': 'help for opt1'} opt_rare = {'section': 'my-section', 'name': 'rare', 'long': 'rare-bool', 'type': bool, 'default': False, 'env_var': 'DOIT_RARE', 'help': 'help for opt2 [default: %(default)s]'} opt_int = {'name': 'num', 'short':'n', 'long': 'number', 'type': int, 'default': 5, 'help': 'help for opt3 [default: %(default)s]'} opt_no = {'name': 'no', 'short':'', 'long': '', 'type': int, 'default': 5, 'help': 'user cant modify me'} class SampleCmd(Command): doc_purpose = 'PURPOSE-X' doc_usage = 'USAGE-X' doc_description = 'DESCRIPTION-X' cmd_options = [opt_bool, opt_rare, opt_int, opt_no] @staticmethod def execute(params, args): return params, args class TestCommand(object): def test_configure(self): config = {'GLOBAL':{'foo':1, 'bar':'2'}, 'whatever':{'xxx': 'yyy'}, 'samplecmd': {'foo':4}, } cmd = SampleCmd(config=config) assert cmd.config == config assert cmd.config_vals == {'foo':4, 'bar':'2'} def test_call_value_cmd_line_arg(self): cmd = SampleCmd() params, args = cmd.parse_execute(['-n','7','ppp']) assert ['ppp'] == args assert 7 == params['num'] def test_call_value_option_default(self): cmd = SampleCmd() params, args = cmd.parse_execute([]) assert 5 == params['num'] def test_call_value_overwritten_default(self): cmd = SampleCmd(config={'GLOBAL':{'num': 20}}) params, args = cmd.parse_execute([]) assert 20 == params['num'] def test_help(self): cmd = SampleCmd(config={'GLOBAL':{'num': 20}}) text = cmd.help() assert 'PURPOSE-X' in text assert 'USAGE-X' in text assert 'DESCRIPTION-X' in text assert '-f' in text assert '--rare-bool' in text assert 'help for opt1' in text assert 'my-section' in text assert opt_no['name'] in [o.name for o in cmd.get_options()] assert "DOIT_RARE" in text # option wihtout short and long are not displayed assert 'user cant modify me' not in text # default value is displayed assert "help for opt2 [default: False]" in text # overwritten default assert "help for opt3 [default: 20]" in text def test_failCall(self): cmd = SampleCmd() pytest.raises(CmdParseError, cmd.parse_execute, ['-x','35']) class TestModuleTaskLoader(object): def test_load_tasks_from_dict(self): cmd = Command() members = {'task_xxx1': lambda : {'actions':[]}, 'task_no': 'strings are not tasks', 'blabla': lambda :None, 'DOIT_CONFIG': {'verbose': 2}, } loader = ModuleTaskLoader(members) loader.setup({}) config = loader.load_doit_config() task_list = loader.load_tasks(cmd, []) assert ['xxx1'] == [t.name for t in task_list] assert {'verbose': 2} == config def test_load_tasks_from_module(self): import tests.module_with_tasks as module loader = ModuleTaskLoader(module) loader.setup({}) config = loader.load_doit_config() task_list = loader.load_tasks(Command(), []) assert ['xxx1'] == [t.name for t in task_list] assert {'verbose': 2} == config def test_task_opt_from_api_to_creator(self): cmd = Command() @task_params([{'name': 'x', 'long': 'x', 'default': None}]) def creator(x): return { 'actions': None, 'targets': [x], } members = { 'task_foo': creator, } loader = ModuleTaskLoader(members) loader.setup({}) loader.task_opts = {'foo': {'x': 'dep'}} task_list = loader.load_tasks(cmd, []) task = task_list.pop() assert task.targets[0] == 'dep' def test_task_config(self): # Ensure that doit.cfg specified task parameters are applied. cmd = Command() members = { 'task_foo': lambda: {'actions':[], 'params': [{ 'name': 'x', 'default': None, 'long': 'x' }]}, 'DOIT_CONFIG': {'task:foo': {'x': 1}}, } loader = ModuleTaskLoader(members) loader.setup({}) loader.config = loader.load_doit_config() task_list = loader.load_tasks(cmd, []) task = task_list.pop() task.init_options() assert 1 == task.options['x'] def test_task_opt_from_api_to_action(self): cmd = Command() members = { 'task_foo': lambda: { 'actions':[], 'params': [{ 'name': 'x', 'default': None, 'long': 'x' }]}, } loader = ModuleTaskLoader(members) loader.setup({}) loader.task_opts = {'foo': {'x': 2}} task_list = loader.load_tasks(cmd, []) task = task_list.pop() task.init_options() assert 2 == task.options['x'] class TestDodoTaskLoader(object): def test_load_tasks(self, restore_cwd): os.chdir(os.path.dirname(__file__)) cmd = Command() params = {'dodoFile': 'loader_sample.py', 'cwdPath': None, 'seek_file': False, } loader = DodoTaskLoader() loader.setup(params) config = loader.load_doit_config() task_list = loader.load_tasks(cmd, []) assert ['xxx1', 'yyy2'] == [t.name for t in task_list] assert {'verbose': 2} == config class TestDoitCmdBase(object): class MyCmd(DoitCmdBase): doc_purpose = "fake for testing" doc_usage = "[TASK ...]" doc_description = None opt_my = { 'name': 'my_opt', 'short':'m', 'long': 'mine', 'type': str, 'default': 'xxx', 'help': "my option" } cmd_options = (opt_my,) def _execute(self, my_opt): return my_opt # command with lower level execute() method def test_new_cmd(self): class MyRawCmd(self.MyCmd): def execute(self, params, args): return params['my_opt'] members = {'task_xxx1': lambda : {'actions':[]},} cmds = {'foo':None, 'bar':None} loader = get_loader({}, task_loader=ModuleTaskLoader(members), cmds=cmds) mycmd = MyRawCmd(task_loader=loader) assert mycmd.loader.cmd_names == ['bar', 'foo'] assert 'min' == mycmd.parse_execute(['--mine', 'min']) # command with _execute() method def test_execute(self, depfile_name): members = {'task_xxx1': lambda : {'actions':[]},} loader = get_loader({}, task_loader=ModuleTaskLoader(members)) mycmd = self.MyCmd(task_loader=loader) assert 'min' == mycmd.parse_execute([ '--db-file', depfile_name, '--mine', 'min']) @mock.patch('doit.cmd_base.Globals') def test_execute_provides_dep_manager(self, mock_globals, depfile_name): mock_globals.dep_manager = None members = {'task_xxx1': lambda: {'actions': []}} class MockTaskLoader(ModuleTaskLoader): def load_tasks(self, cmd, pos_args): # ensure dep_manager is set before tasks are loaded: assert mock_globals.dep_manager return super().load_tasks(cmd, pos_args) loader = get_loader({}, task_loader=MockTaskLoader(members)) mycmd = self.MyCmd(task_loader=loader) mycmd.parse_execute(['--db-file', depfile_name, '--mine', 'min']) assert mock_globals.dep_manager == mycmd.dep_manager # command with _execute() method def test_minversion(self, depfile_name, monkeypatch): members = { 'task_xxx1': lambda : {'actions':[]}, 'DOIT_CONFIG': {'minversion': '5.2.3'}, } loader = ModuleTaskLoader(members) # version ok monkeypatch.setattr(version, 'VERSION', '7.5.8') mycmd = self.MyCmd(task_loader=loader) assert 'xxx' == mycmd.parse_execute(['--db-file', depfile_name]) # version too old monkeypatch.setattr(version, 'VERSION', '5.2.1') mycmd = self.MyCmd(task_loader=loader) pytest.raises(InvalidDodoFile, mycmd.parse_execute, []) def testInvalidChecker(self): mycmd = self.MyCmd(task_loader=ModuleTaskLoader({})) params, args = CmdParse(mycmd.get_options()).parse([]) params['check_file_uptodate'] = 'i dont exist' pytest.raises(InvalidCommand, mycmd.execute, params, args) def testCustomChecker(self, depfile_name): class MyChecker(FileChangedChecker): pass mycmd = self.MyCmd(task_loader=ModuleTaskLoader({})) params, args = CmdParse(mycmd.get_options()).parse([]) params['check_file_uptodate'] = MyChecker params['dep_file'] = depfile_name mycmd.execute(params, args) assert isinstance(mycmd.dep_manager.checker, MyChecker) def testCustomCodec(self, depfile_name): class MyCodec(JSONCodec): pass mycmd = self.MyCmd(task_loader=ModuleTaskLoader({})) params, args = CmdParse(mycmd.get_options()).parse([]) params['codec_cls'] = MyCodec params['dep_file'] = depfile_name mycmd.execute(params, args) assert isinstance(mycmd.dep_manager.backend.codec, MyCodec) def testPluginBackend(self, depfile_name): mycmd = self.MyCmd(task_loader=ModuleTaskLoader({}), config={'BACKEND': {'j2': 'doit.dependency:JsonDB'}}) params, args = CmdParse(mycmd.get_options()).parse(['--backend', 'j2']) params['dep_file'] = depfile_name mycmd.execute(params, args) assert mycmd.dep_manager.db_class is mycmd._backends['j2'] def testPluginLoader(self): entry_point = {'mod': 'tests.sample_plugin:MyLoader'} config = { 'GLOBAL': {'loader': 'mod'}, 'LOADER': entry_point, } loader = get_loader(config) mycmd = self.MyCmd(task_loader=loader, config=config) assert mycmd.loader.__class__.__name__ == 'MyLoader' dodo_config = mycmd.loader.load_doit_config() task_list = mycmd.loader.load_tasks(mycmd, []) assert task_list[0].name == 'sample_task' assert dodo_config == {'verbosity': 2} def test_force_verbosity(self, dep_manager): members = { 'DOIT_CONFIG': {'verbosity': 0}, 'task_xxx1': lambda : {'actions':[]}, } loader = ModuleTaskLoader(members) class SampleCmd(DoitCmdBase): opt_verbosity = { 'name':'verbosity', 'short':'v', 'long':'verbosity', 'type':int, 'default': None, 'help': "verbosity foo" } cmd_options = (opt_verbosity, ) def _execute(self, verbosity, force_verbosity): return verbosity, force_verbosity cmd = CmdFactory(SampleCmd, task_loader=loader, dep_manager=dep_manager) assert (2, True) == cmd.parse_execute( ['--db-file', dep_manager.name, '-v2']) assert (0, False) == cmd.parse_execute(['--db-file', dep_manager.name]) class TestCheckTasksExist(object): def test_None(self): check_tasks_exist({}, None) # nothing is raised def test_invalid(self): pytest.raises(InvalidCommand, check_tasks_exist, {}, 't2') def test_valid(self): tasks = { 't1': Task("t1", [""] ), 't2': Task("t2", [""], task_dep=['t1']), } check_tasks_exist(tasks, ['t2']) # nothing is raised class TestTaskAndDepsIter(object): def test_dep_iter(self): tasks = { 't1': Task("t1", [""] ), 't2': Task("t2", [""], task_dep=['t1']), 't3': Task("t3", [""], setup=['t1']), 't4': Task("t4", [""], task_dep=['t3']), } def names(sel_tasks, repeated=False): task_list = tasks_and_deps_iter(tasks, sel_tasks, repeated) return [t.name for t in task_list] # no deps assert ['t1'] == names(['t1']) # with task_dep assert ['t2', 't1'] == names(['t2']) # with setup assert ['t3', 't1'] == names(['t3']) # two levels assert ['t4', 't3', 't1'] == names(['t4']) # select 2 assert set(['t2', 't1']) == set(names(['t1', 't2'])) # repeat deps got = names(['t1', 't2'], True) assert 3 == len(got) assert 't1' == got[-1] class TestSubtaskIter(object): def test_sub_iter(self): tasks = { 't1': Task("t1", [""] ), 't1:x': Task("t1:x", [""], subtask_of='t1'), 't2': Task("t2", [""], task_dep=['t1', 't1:x', 't2:a', 't2:b']), 't2:a': Task("t2:a", [""], subtask_of='t2'), 't2:b': Task("t2:b", [""], subtask_of='t2'), } def names(task_name): return [t.name for t in subtasks_iter(tasks, tasks[task_name])] assert [] == names('t1') assert ['t2:a', 't2:b'] == names('t2') doit-0.36.0/tests/test_cmd_clean.py000066400000000000000000000145671423054503100172310ustar00rootroot00000000000000from io import StringIO from unittest import mock import pytest from doit.exceptions import InvalidCommand from doit.task import Task from doit.cmd_clean import Clean from .conftest import CmdFactory class TestCmdClean(object): @pytest.fixture def tasks(self, request): self.cleaned = [] def myclean(name): self.cleaned.append(name) return [ Task("t1", None, targets=['t1.out'], setup=['t2'], clean=[(myclean,('t1',))]), Task("t2", None, clean=[(myclean,('t2',))]), Task("t3", None, task_dep=['t3:a'], has_subtask=True, clean=[(myclean,('t3',))]), Task("t3:a", None, clean=[(myclean,('t3:a',))], subtask_of='t3'), Task("t4", None, file_dep=['t1.out'], clean=[(myclean,('t4',))] ), ] def test_clean_all(self, tasks): output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=True, cleanforget=False) # all enables --clean-dep assert ['t4', 't1', 't2', 't3', 't3:a'] == self.cleaned def test_clean_default_all(self, tasks): output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=False) # default enables --clean-dep assert ['t4', 't1', 't2', 't3', 't3:a'] == self.cleaned def test_clean_default(self, tasks): output = StringIO() cmd_clean = CmdFactory( Clean, outstream=output, task_list=tasks, sel_tasks=['t1'], dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=False) # default enables --clean-dep assert ['t1', 't2'] == self.cleaned def test_clean_selected(self, tasks): output = StringIO() mock_dep_manager = mock.MagicMock() cmd_clean = CmdFactory( Clean, outstream=output, task_list=tasks, sel_tasks=['t1'], dep_manager=mock_dep_manager) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=False, pos_args=['t2']) assert ['t2'] == self.cleaned mock_dep_manager.remove.assert_not_called() def test_clean_selected_wildcard(self, tasks): output = StringIO() mock_dep_manager = mock.MagicMock() cmd_clean = CmdFactory( Clean, outstream=output, task_list=tasks, dep_manager=mock_dep_manager) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=False, pos_args=['t3*']) assert ['t3', 't3:a'] == self.cleaned mock_dep_manager.remove.assert_not_called() def test_clean_taskdep(self, tasks): output = StringIO() mock_dep_manager = mock.MagicMock() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock_dep_manager) cmd_clean._execute(dryrun=False, cleandep=True, cleanall=False, cleanforget=False, pos_args=['t1']) assert ['t1', 't2'] == self.cleaned mock_dep_manager.remove.assert_not_called() def test_clean_taskdep_recursive(self, tasks): output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=True, cleanall=False, cleanforget=False, pos_args=['t4']) assert ['t4', 't1', 't2'] == self.cleaned def test_clean_subtasks(self, tasks): output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=False, pos_args=['t3']) assert ['t3', 't3:a'] == self.cleaned def test_clean_taskdep_once(self, tasks): # do not execute clean operation more than once output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock.MagicMock()) cmd_clean._execute(dryrun=False, cleandep=True, cleanall=False, cleanforget=False, pos_args=['t1', 't2']) assert ['t1', 't2'] == self.cleaned def test_clean_invalid_task(self, tasks): output = StringIO() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, sel_tasks=['t1']) pytest.raises(InvalidCommand, cmd_clean._execute, dryrun=False, cleandep=False, cleanall=False, cleanforget=False, pos_args=['xxxx']) def test_clean_forget_selected(self, tasks): output = StringIO() mock_dep_manager = mock.MagicMock() cmd_clean = CmdFactory( Clean, outstream=output, task_list=tasks, sel_tasks=['t1'], dep_manager=mock_dep_manager) cmd_clean._execute(dryrun=False, cleandep=False, cleanall=False, cleanforget=True, pos_args=['t2']) assert ['t2'] == self.cleaned # order mock_dep_manager.assert_has_calls( [mock.call.remove(mock.ANY), mock.call.close()]) # exactly t2, not more assert (mock_dep_manager.remove.call_args_list == [mock.call('t2')]) def test_clean_forget_taskdep(self, tasks): output = StringIO() mock_dep_manager = mock.MagicMock() cmd_clean = CmdFactory(Clean, outstream=output, task_list=tasks, dep_manager=mock_dep_manager) cmd_clean._execute(dryrun=False, cleandep=True, cleanall=False, cleanforget=True, pos_args=['t1']) assert ['t1', 't2'] == self.cleaned # order mock_dep_manager.assert_has_calls( [mock.call.remove(mock.ANY), mock.call.close()]) assert (mock_dep_manager.remove.call_args_list == [mock.call('t1'), mock.call('t2')]) doit-0.36.0/tests/test_cmd_completion.py000066400000000000000000000120431423054503100203030ustar00rootroot00000000000000from io import StringIO import pytest from doit.exceptions import InvalidCommand from doit.cmdparse import CmdOption from doit.plugin import PluginDict from doit.task import Task from doit.cmd_base import Command, DodoTaskLoader, TaskLoader2 from doit.cmd_completion import TabCompletion from doit.cmd_help import Help from .conftest import CmdFactory # doesnt test the shell scripts. just test its creation! class FakeLoader2(TaskLoader2): def load_doit_config(self): return {} def load_tasks(self, cmd, pos_args): task_list = [ Task("t1", None, ), Task("t2", None, task_dep=['t2:a'], has_subtask=True, ), Task("t2:a", None, subtask_of='t2'), ] return task_list @pytest.fixture def commands(request): sub_cmds = {} sub_cmds['tabcompletion'] = TabCompletion sub_cmds['help'] = Help return PluginDict(sub_cmds) def test_invalid_shell_option(): cmd = CmdFactory(TabCompletion) pytest.raises(InvalidCommand, cmd.execute, {'shell':'another_shell', 'hardcode_tasks': False}, []) class TestCmdCompletionBash(object): def test_with_dodo__dynamic_tasks(self, commands): output = StringIO() cmd = CmdFactory(TabCompletion, task_loader=DodoTaskLoader(), outstream=output, cmds=commands) cmd.execute({'shell':'bash', 'hardcode_tasks': False}, []) got = output.getvalue() assert 'dodof' in got assert 't1' not in got assert 'tabcompletion' in got @pytest.mark.parametrize('loader_class', [FakeLoader2]) def test_no_dodo__hardcoded_tasks(self, commands, loader_class): output = StringIO() cmd = CmdFactory(TabCompletion, task_loader=loader_class(), outstream=output, cmds=commands) cmd.execute({'shell':'bash', 'hardcode_tasks': True}, []) got = output.getvalue() assert 'dodo.py' not in got assert 't1' in got def test_cmd_takes_file_args(self, commands): output = StringIO() cmd = CmdFactory(TabCompletion, task_loader=FakeLoader2(), outstream=output, cmds=commands) cmd.execute({'shell':'bash', 'hardcode_tasks': False}, []) got = output.getvalue() assert """help) COMPREPLY=( $(compgen -W "${tasks} ${sub_cmds}" -- $cur) ) return 0""" in got assert """tabcompletion) COMPREPLY=( $(compgen -f -- $cur) ) return 0""" in got class TestCmdCompletionZsh(object): def test_zsh_arg_line(self): opt1 = CmdOption({'name':'o1', 'default':'', 'help':'my desc'}) assert '' == TabCompletion._zsh_arg_line(opt1) opt2 = CmdOption({'name':'o2', 'default':'', 'help':'my desc', 'short':'s'}) assert '"-s[my desc]" \\' == TabCompletion._zsh_arg_line(opt2) opt3 = CmdOption({'name':'o3', 'default':'', 'help':'my desc', 'long':'lll'}) assert '"--lll[my desc]" \\' == TabCompletion._zsh_arg_line(opt3) opt4 = CmdOption({'name':'o4', 'default':'', 'help':'my desc [b]a', 'short':'s', 'long':'lll'}) assert ('"(-s|--lll)"{-s,--lll}"[my desc [b\\]a]" \\' == TabCompletion._zsh_arg_line(opt4)) # escaping `"` test opt5 = CmdOption({'name':'o5', 'default':'', 'help':'''my "des'c [b]a''', 'short':'s', 'long':'lll'}) assert ('''"(-s|--lll)"{-s,--lll}"[my \\"des'c [b\\]a]" \\''' == TabCompletion._zsh_arg_line(opt5)) def test_cmd_arg_list(self): no_args = TabCompletion._zsh_arg_list(Command()) assert "'*::task:(($tasks))'" not in no_args assert "'::cmd:(($commands))'" not in no_args class CmdTakeTasks(Command): doc_usage = '[TASK ...]' with_task_args = TabCompletion._zsh_arg_list(CmdTakeTasks()) assert "'*::task:(($tasks))'" in with_task_args assert "'::cmd:(($commands))'" not in with_task_args class CmdTakeCommands(Command): doc_usage = '[COMMAND ...]' with_cmd_args = TabCompletion._zsh_arg_list(CmdTakeCommands()) assert "'*::task:(($tasks))'" not in with_cmd_args assert "'::cmd:(($commands))'" in with_cmd_args def test_cmds_with_params(self, commands): output = StringIO() cmd = CmdFactory(TabCompletion, task_loader=DodoTaskLoader(), outstream=output, cmds=commands) cmd.execute({'shell':'zsh', 'hardcode_tasks': False}, []) got = output.getvalue() assert "tabcompletion: generate script" in got @pytest.mark.parametrize('loader_class', [FakeLoader2]) def test_hardcoded_tasks(self, commands, loader_class): output = StringIO() cmd = CmdFactory(TabCompletion, task_loader=loader_class(), outstream=output, cmds=commands) cmd.execute({'shell':'zsh', 'hardcode_tasks': True}, []) got = output.getvalue() assert 't1' in got doit-0.36.0/tests/test_cmd_dumpdb.py000066400000000000000000000011511423054503100174030ustar00rootroot00000000000000import pytest from doit.cmd_dumpdb import DumpDB class TestCmdDumpDB(object): def testDefault(self, capsys, dep_manager): if dep_manager.whichdb in ('dbm', 'dbm.ndbm'): # pragma: no cover pytest.skip('%s not supported for this operation' % dep_manager.whichdb) # cmd_main(["help", "task"]) dep_manager._set('tid', 'my_dep', 'xxx') dep_manager.close() cmd_dump = DumpDB() cmd_dump.execute({'dep_file': dep_manager.name}, []) out, err = capsys.readouterr() assert 'tid' in out assert 'my_dep' in out assert 'xxx' in out doit-0.36.0/tests/test_cmd_forget.py000066400000000000000000000123671423054503100174310ustar00rootroot00000000000000from io import StringIO import pytest from doit.exceptions import InvalidCommand from doit.dependency import DbmDB, Dependency from doit.cmd_forget import Forget from .conftest import tasks_sample, CmdFactory class TestCmdForget(object): @pytest.fixture def tasks(self, request): return tasks_sample() @staticmethod def _add_task_deps(tasks, testdb): """put some data on testdb""" dep = Dependency(DbmDB, testdb) for task in tasks: dep._set(task.name,"dep","1") dep.close() dep2 = Dependency(DbmDB, testdb) assert "1" == dep2._get("g1.a", "dep") dep2.close() def testForgetDefault(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory(Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=['t1', 't2'], ) cmd_forget._execute(False, False, False) got = output.getvalue().split("\n")[:-1] assert ["forgetting t1", "forgetting t2"] == got, repr(output.getvalue()) dep = Dependency(DbmDB, depfile_name) assert None == dep._get('t1', "dep") assert None == dep._get('t2', "dep") assert '1' == dep._get('t3', "dep") def testForgetAll(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory(Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=[]) cmd_forget._execute(False, False, True) got = output.getvalue().split("\n")[:-1] assert ["forgetting all tasks"] == got, repr(output.getvalue()) dep = Dependency(DbmDB, depfile_name) for task in tasks: assert None == dep._get(task.name, "dep") def testDisableDefault(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory(Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=['t1', 't2'], sel_default_tasks=True) cmd_forget._execute(False, True, False) got = output.getvalue().split("\n")[:-1] assert ["no tasks specified, pass task name, --enable-default or --all"] == got, ( repr(output.getvalue())) dep = Dependency(DbmDB, depfile_name) for task in tasks: assert "1" == dep._get(task.name, "dep") def testForgetOne(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory(Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=["t2", "t1"]) cmd_forget._execute(False, True, False) got = output.getvalue().split("\n")[:-1] assert ["forgetting t2", "forgetting t1"] == got dep = Dependency(DbmDB, depfile_name) assert None == dep._get("t1", "dep") assert None == dep._get("t2", "dep") assert "1" == dep._get("g1.a", "dep") def testForgetGroup(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory( Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=["g1"]) cmd_forget._execute(False, False, False) got = output.getvalue().split("\n")[:-1] assert "forgetting g1" == got[0] dep = Dependency(DbmDB, depfile_name) assert "1" == dep._get("t1", "dep") assert "1" == dep._get("t2", "dep") assert None == dep._get("g1", "dep") assert None == dep._get("g1.a", "dep") assert None == dep._get("g1.b", "dep") def testForgetTaskDependency(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory( Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=["t3"]) cmd_forget._execute(True, False, False) dep = Dependency(DbmDB, depfile_name) assert None == dep._get("t3", "dep") assert None == dep._get("t1", "dep") # if task dependency not from a group dont forget it def testDontForgetTaskDependency(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory( Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=["t3"]) cmd_forget._execute(False, False, False) dep = Dependency(DbmDB, depfile_name) assert None == dep._get("t3", "dep") assert "1" == dep._get("t1", "dep") def testForgetInvalid(self, tasks, depfile_name): self._add_task_deps(tasks, depfile_name) output = StringIO() cmd_forget = CmdFactory( Forget, outstream=output, dep_file=depfile_name, backend='dbm', task_list=tasks, sel_tasks=["XXX"]) pytest.raises(InvalidCommand, cmd_forget._execute, False, False, False) doit-0.36.0/tests/test_cmd_help.py000066400000000000000000000051141423054503100170630ustar00rootroot00000000000000from doit.doit_cmd import DoitMain def cmd_main(args, extra_config=None, bin_name='doit'): if extra_config: extra_config = {'GLOBAL': extra_config} main = DoitMain(extra_config=extra_config) main.BIN_NAME = bin_name return main.run(args) class TestHelp(object): def test_help_usage(self, capsys): returned = cmd_main(["help"]) assert returned == 0 out, err = capsys.readouterr() assert "doit list" in out def test_help_usage_custom_name(self, capsys): returned = cmd_main(["help"], bin_name='mytool') assert returned == 0 out, err = capsys.readouterr() assert "mytool list" in out def test_help_plugin_name(self, capsys): plugin = {'XXX': 'tests.sample_plugin:MyCmd'} main = DoitMain(extra_config={'COMMAND':plugin}) main.BIN_NAME = 'doit' returned = main.run(["help"]) assert returned == 0 out, err = capsys.readouterr() assert "doit XXX " in out assert "test extending doit commands" in out, out def test_help_task_params(self, capsys): returned = cmd_main(["help", "task"]) assert returned == 0 out, err = capsys.readouterr() assert "Task Dictionary parameters" in out def test_help_cmd(self, capsys): returned = cmd_main(["help", "list"], {'dep_file': 'foo.db'}) assert returned == 0 out, err = capsys.readouterr() assert "PURPOSE" in out assert "list tasks from dodo file" in out # overwritten defaults, are shown as default assert "file used to save successful runs [default: foo.db]" in out def test_help_task_name(self, capsys, restore_cwd, depfile_name): returned = cmd_main(["help", "-f", "tests/loader_sample.py", "--db-file", depfile_name, "xxx1"]) assert returned == 0 out, err = capsys.readouterr() assert "xxx1" in out # name assert "task doc" in out # doc assert "-p" in out # params def test_help_wrong_name(self, capsys, restore_cwd, depfile_name): returned = cmd_main(["help", "-f", "tests/loader_sample.py", "--db-file", depfile_name, "wrong_name"]) assert returned == 0 # TODO return different value? out, err = capsys.readouterr() assert "doit list" in out def test_help_no_dodo_file(self, capsys): returned = cmd_main(["help", "-f", "no_dodo", "wrong_name"]) assert returned == 0 # TODO return different value? out, err = capsys.readouterr() assert "doit list" in out doit-0.36.0/tests/test_cmd_ignore.py000066400000000000000000000050211423054503100174130ustar00rootroot00000000000000from io import StringIO import pytest from doit.exceptions import InvalidCommand from doit.dependency import DbmDB, Dependency from doit.cmd_ignore import Ignore from .conftest import tasks_sample, CmdFactory class TestCmdIgnore(object): @pytest.fixture def tasks(self, request): return tasks_sample() def testIgnoreAll(self, tasks, dep_manager): output = StringIO() cmd = CmdFactory(Ignore, outstream=output, dep_manager=dep_manager, task_list=tasks) cmd._execute([]) got = output.getvalue().split("\n")[:-1] assert ["You cant ignore all tasks! Please select a task."] == got, got for task in tasks: assert None == dep_manager._get(task.name, "ignore:") def testIgnoreOne(self, tasks, dep_manager): output = StringIO() cmd = CmdFactory(Ignore, outstream=output, dep_manager=dep_manager, task_list=tasks) cmd._execute(["t2", "t1"]) got = output.getvalue().split("\n")[:-1] assert ["ignoring t2", "ignoring t1"] == got dep = Dependency(DbmDB, dep_manager.name) assert '1' == dep._get("t1", "ignore:") assert '1' == dep._get("t2", "ignore:") assert None == dep._get("t3", "ignore:") def testIgnoreGroup(self, tasks, dep_manager): output = StringIO() cmd = CmdFactory(Ignore, outstream=output, dep_manager=dep_manager, task_list=tasks) cmd._execute(["g1"]) got = output.getvalue().split("\n")[:-1] dep = Dependency(DbmDB, dep_manager.name) assert None == dep._get("t1", "ignore:"), got assert None == dep._get("t2", "ignore:") assert '1' == dep._get("g1", "ignore:") assert '1' == dep._get("g1.a", "ignore:") assert '1' == dep._get("g1.b", "ignore:") # if task dependency not from a group dont ignore it def testDontIgnoreTaskDependency(self, tasks, dep_manager): output = StringIO() cmd = CmdFactory(Ignore, outstream=output, dep_manager=dep_manager, task_list=tasks) cmd._execute(["t3"]) dep = Dependency(DbmDB, dep_manager.name) assert '1' == dep._get("t3", "ignore:") assert None == dep._get("t1", "ignore:") def testIgnoreInvalid(self, tasks, dep_manager): output = StringIO() cmd = CmdFactory(Ignore, outstream=output, dep_manager=dep_manager, task_list=tasks) pytest.raises(InvalidCommand, cmd._execute, ["XXX"]) doit-0.36.0/tests/test_cmd_info.py000066400000000000000000000074361423054503100170770ustar00rootroot00000000000000from io import StringIO import pytest from doit.exceptions import InvalidCommand from doit.task import Task from doit.cmd_info import Info from .conftest import CmdFactory class TestCmdInfo(object): def test_info_basic_attrs(self, dep_manager): output = StringIO() task = Task("t1", [], file_dep=['tests/data/dependency1'], doc="task doc", getargs={'a': ('x', 'y')}, verbosity=2, meta={'a': ['b', 'c']}) cmd = CmdFactory(Info, outstream=output, dep_file=dep_manager.name, task_list=[task]) cmd._execute(['t1'], hide_status=True) assert "t1" in output.getvalue() assert "task doc" in output.getvalue() assert "- tests/data/dependency1" in output.getvalue() assert "verbosity : 2" in output.getvalue() assert "getargs : {'a': ('x', 'y')}" in output.getvalue() assert "meta : {'a': ['b', 'c']}" in output.getvalue() def test_invalid_command_args(self, dep_manager): output = StringIO() task = Task("t1", [], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Info, outstream=output, dep_file=dep_manager.name, task_list=[task]) # fails if number of args != 1 pytest.raises(InvalidCommand, cmd._execute, []) pytest.raises(InvalidCommand, cmd._execute, ['t1', 't2']) def test_execute_status_run(self, dep_manager, dependency1): output = StringIO() task = Task("t1", [], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Info, outstream=output, dep_file=dep_manager.name, task_list=[task], dep_manager=dep_manager) return_val = cmd._execute(['t1']) assert "t1" in output.getvalue() assert return_val == 1 # indicates task is not up-to-date assert "status" in output.getvalue() assert ": run" in output.getvalue() assert " - tests/data/dependency1" in output.getvalue() def test_hide_execute_status(self, dep_manager, dependency1): output = StringIO() task = Task("t1", [], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Info, outstream=output, dep_manager=dep_manager, task_list=[task]) return_val = cmd._execute(['t1'], hide_status=True) assert "t1" in output.getvalue() assert return_val == 0 # always zero if not showing status assert "status" not in output.getvalue() assert ": run" not in output.getvalue() def test_execute_status_uptodate(self, dep_manager, dependency1): output = StringIO() task = Task("t1", [], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Info, outstream=output, dep_manager=dep_manager, task_list=[task]) cmd.dep_manager.save_success(task) return_val = cmd._execute(['t1']) assert "t1" in output.getvalue() assert return_val == 0 # indicates task is not up-to-date assert ": up-to-date" in output.getvalue() def test_get_reasons_str(self): reasons = { 'has_no_dependencies': True, 'uptodate_false': [('func', 'arg', 'kwarg')], 'checker_changed': ['foo', 'bar'], 'missing_target': ['f1', 'f2'], } got = Info.get_reasons(reasons).splitlines() assert len(got) == 7 assert got[0] == ' * The task has no dependencies.' assert got[1] == ' * The following uptodate objects evaluate to false:' assert got[2] == ' - func (args=arg, kwargs=kwarg)' assert got[3] == ' * The file_dep checker changed from foo to bar.' assert got[4] == ' * The following targets do not exist:' assert got[5] == ' - f1' assert got[6] == ' - f2' doit-0.36.0/tests/test_cmd_list.py000066400000000000000000000162161423054503100171130ustar00rootroot00000000000000from io import StringIO import pytest from doit.exceptions import InvalidCommand from doit.task import Task from doit.tools import result_dep from doit.cmd_list import List from tests.conftest import tasks_sample, tasks_bad_sample, CmdFactory class TestCmdList(object): def testQuiet(self): output = StringIO() tasks = tasks_sample() cmd_list = CmdFactory(List, outstream=output, task_list=tasks) cmd_list._execute() got = [line.strip() for line in output.getvalue().split('\n') if line] expected = [t.name for t in tasks if not t.subtask_of] assert sorted(expected) == got def testDoc(self): output = StringIO() tasks = tasks_sample() cmd_list = CmdFactory(List, outstream=output, task_list=tasks) cmd_list._execute(quiet=False) got = [line for line in output.getvalue().split('\n') if line] expected = [] for t in sorted(tasks): if not t.subtask_of: expected.append([t.name, t.doc]) assert len(expected) == len(got) for exp1, got1 in zip(expected, got): assert exp1 == got1.split(None, 1) def testCustomTemplate(self): output = StringIO() tasks = tasks_sample() cmd_list = CmdFactory(List, outstream=output, task_list=tasks) cmd_list._execute(template='xxx {name} xxx {doc}') got = [line.strip() for line in output.getvalue().split('\n') if line] assert 'xxx g1 xxx g1 doc string' == got[0] assert 'xxx t3 xxx t3 doc string' == got[3] def testDependencies(self): my_task = Task("t2", [""], file_dep=['d2.txt']) output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=[my_task]) cmd_list._execute(list_deps=True) got = output.getvalue() assert "d2.txt" in got def testSubTask(self): output = StringIO() tasks = tasks_sample() cmd_list = CmdFactory(List, outstream=output, task_list=tasks) cmd_list._execute(subtasks=True) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = [t.name for t in sorted(tasks)] assert expected == got def testFilter(self): output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=tasks_sample()) cmd_list._execute(pos_args=['g1', 't2']) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['g1', 't2'] assert expected == got def testFilterSubtask(self): output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=tasks_sample()) cmd_list._execute(pos_args=['g1.a']) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['g1.a'] assert expected == got def testFilterAll(self): output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=tasks_sample()) cmd_list._execute(subtasks=True, pos_args=['g1']) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['g1', 'g1.a', 'g1.b'] assert expected == got def testStatus(self, dependency1, dep_manager): task_list = tasks_sample() dep_manager.ignore(task_list[0]) # t1 dep_manager.save_success(task_list[1]) # t2 dep_manager.close() output = StringIO() cmd_list = CmdFactory(List, outstream=output, dep_file=dep_manager.name, backend='dbm', task_list=task_list) cmd_list._execute(status=True) got = [line.strip() for line in output.getvalue().split('\n') if line] assert 'R g1' in got assert 'I t1' in got assert 'U t2' in got def testErrorStatus(self, dependency1, dep_manager): """Check that problematic tasks show an 'E' as status.""" task_list = tasks_bad_sample() output = StringIO() cmd_list = CmdFactory(List, outstream=output, dep_manager=dep_manager, task_list=task_list) cmd_list._execute(status=True) for line in output.getvalue().split('\n'): if line: assert line.strip().startswith('E ') def testStatus_result_dep_bug_gh44(self, dependency1, dep_manager): # make sure task dict is passed when checking up-to-date task_list = [Task("t1", [""], doc="t1 doc string"), Task("t2", [""], uptodate=[result_dep('t1')]),] dep_manager.save_success(task_list[0]) # t1 dep_manager.close() output = StringIO() cmd_list = CmdFactory(List, outstream=output, dep_file=dep_manager.name, backend='dbm', task_list=task_list) cmd_list._execute(status=True) got = [line.strip() for line in output.getvalue().split('\n') if line] assert 'R t1' in got assert 'R t2' in got def testNoPrivate(self): task_list = list(tasks_sample()) task_list.append(Task("_s3", [""])) output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=task_list) cmd_list._execute(pos_args=['_s3']) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = [] assert expected == got def testWithPrivate(self): task_list = list(tasks_sample()) task_list.append(Task("_s3", [""])) output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=task_list) cmd_list._execute(private=True, pos_args=['_s3']) got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['_s3'] assert expected == got def testListInvalidTask(self): output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=tasks_sample()) pytest.raises(InvalidCommand, cmd_list._execute, pos_args=['xxx']) def test_unicode_name(self, dep_manager): task_list = [Task("t做", [""], doc="t1 doc string 做"),] output = StringIO() cmd_list = CmdFactory(List, outstream=output, dep_file=dep_manager.name, task_list=task_list) cmd_list._execute() got = [line.strip() for line in output.getvalue().split('\n') if line] assert 't做' == got[0] def testSortByName(self): # by default, the task list should be ordered by name task_list = list(tasks_sample()) output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=task_list) cmd_list._execute() got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['g1', 't1', 't2', 't3'] assert expected == got def testSortByDefinition(self): # test sorting task list by order of definition task_list = list(tasks_sample()) output = StringIO() cmd_list = CmdFactory(List, outstream=output, task_list=task_list) cmd_list._execute(sort='definition') got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ['t1', 't2', 'g1', 't3'] assert expected == got doit-0.36.0/tests/test_cmd_resetdep.py000066400000000000000000000121551423054503100177510ustar00rootroot00000000000000from io import StringIO import pytest from doit.cmd_resetdep import ResetDep from doit.dependency import TimestampChecker, get_md5, get_file_md5 from doit.exceptions import InvalidCommand from doit.task import Task from tests.conftest import tasks_sample, CmdFactory, get_abspath class TestCmdResetDep(object): def test_execute(self, dep_manager, dependency1): output = StringIO() tasks = tasks_sample() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=tasks, dep_manager=dep_manager) cmd_reset._execute() got = [line.strip() for line in output.getvalue().split('\n') if line] expected = ["processed %s" % t.name for t in tasks] assert sorted(expected) == sorted(got) def test_file_dep(self, dep_manager, dependency1): my_task = Task("t2", [""], file_dep=['tests/data/dependency1']) output = StringIO() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=[my_task], dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert "processed t2\n" == got dep = list(my_task.file_dep)[0] timestamp, size, md5 = dep_manager._get(my_task.name, dep) assert get_file_md5(get_abspath("data/dependency1")) == md5 def test_file_dep_up_to_date(self, dep_manager, dependency1): my_task = Task("t2", [""], file_dep=['tests/data/dependency1']) dep_manager.save_success(my_task) output = StringIO() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=[my_task], dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert "skip t2\n" == got def test_file_dep_change_checker(self, dep_manager, dependency1): my_task = Task("t2", [""], file_dep=['tests/data/dependency1']) dep_manager.save_success(my_task) dep_manager.checker = TimestampChecker() output = StringIO() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=[my_task], dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert "processed t2\n" == got def test_filter(self, dep_manager, dependency1): output = StringIO() tasks = tasks_sample() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=tasks, dep_manager=dep_manager) cmd_reset._execute(pos_args=['t2']) got = output.getvalue() assert "processed t2\n" == got def test_invalid_task(self, dep_manager): output = StringIO() tasks = tasks_sample() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=tasks, dep_manager=dep_manager) pytest.raises(InvalidCommand, cmd_reset._execute, pos_args=['xxx']) def test_missing_file_dep(self, dep_manager): my_task = Task("t2", [""], file_dep=['tests/data/missing']) output = StringIO() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=[my_task], dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert ("failed t2 (Dependent file 'tests/data/missing' does not " "exist.)\n") == got def test_missing_dep_and_target(self, dep_manager, dependency1, dependency2): task_a = Task("task_a", [""], file_dep=['tests/data/dependency1'], targets=['tests/data/dependency2']) task_b = Task("task_b", [""], file_dep=['tests/data/dependency2'], targets=['tests/data/dependency3']) task_c = Task("task_c", [""], file_dep=['tests/data/dependency3'], targets=['tests/data/dependency4']) output = StringIO() tasks = [task_a, task_b, task_c] cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=tasks, dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert ("processed task_a\n" "processed task_b\n" "failed task_c (Dependent file 'tests/data/dependency3'" " does not exist.)\n") == got def test_values_and_results(self, dep_manager, dependency1): my_task = Task("t2", [""], file_dep=['tests/data/dependency1']) my_task.result = "result" my_task.values = {'x': 5, 'y': 10} dep_manager.save_success(my_task) dep_manager.checker = TimestampChecker() # trigger task update reseted = Task("t2", [""], file_dep=['tests/data/dependency1']) output = StringIO() cmd_reset = CmdFactory(ResetDep, outstream=output, task_list=[reseted], dep_manager=dep_manager) cmd_reset._execute() got = output.getvalue() assert "processed t2\n" == got assert {'x': 5, 'y': 10} == dep_manager.get_values(reseted.name) assert get_md5('result') == dep_manager.get_result(reseted.name) doit-0.36.0/tests/test_cmd_run.py000066400000000000000000000147641423054503100167520ustar00rootroot00000000000000import os from io import StringIO from unittest.mock import Mock import pytest from doit.exceptions import InvalidCommand from doit import reporter, runner from doit.cmd_run import Run from tests.conftest import tasks_sample, CmdFactory class TestCmdRun(object): def testProcessRun(self, dependency1, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample()) result = cmd_run._execute(output) assert 0 == result got = output.getvalue().split("\n")[:-1] assert [". t1", ". t2", ". g1.a", ". g1.b", ". t3"] == got @pytest.mark.skipif('not runner.MRunner.available()') def testProcessRunMP(self, dependency1, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample()) result = cmd_run._execute(output, num_process=1) assert 0 == result got = output.getvalue().split("\n")[:-1] assert [". t1", ". t2", ". g1.a", ". g1.b", ". t3"] == got def testProcessRunMThread(self, dependency1, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample()) result = cmd_run._execute(output, num_process=1, par_type='thread') assert 0 == result got = output.getvalue().split("\n")[:-1] assert [". t1", ". t2", ". g1.a", ". g1.b", ". t3"] == got def testInvalidParType(self, dependency1, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample()) pytest.raises(InvalidCommand, cmd_run._execute, output, num_process=1, par_type='not_exist') def testMP_not_available(self, dependency1, depfile_name, capsys, monkeypatch): # make sure MRunner wont be used monkeypatch.setattr(runner.MRunner, "available", Mock(return_value=False)) output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample()) result = cmd_run._execute(output, num_process=1) assert 0 == result got = output.getvalue().split("\n")[:-1] assert [". t1", ". t2", ". g1.a", ". g1.b", ". t3"] == got err = capsys.readouterr()[1] assert "WARNING:" in err assert "parallel using threads" in err def testProcessRunFilter(self, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample(), sel_tasks=["g1.a"]) cmd_run._execute(output) got = output.getvalue().split("\n")[:-1] assert [". g1.a"] == got def testProcessRunSingle(self, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample(), sel_tasks=["t3"]) cmd_run._execute(output, single=True) got = output.getvalue().split("\n")[:-1] # t1 is a depenendency of t3 but not included assert [". t3"] == got def testProcessRunSingleSubtasks(self, depfile_name): output = StringIO() task_list = tasks_sample() assert task_list[4].name == 'g1.b' task_list[4].task_dep = ['t3'] cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=task_list, sel_tasks=["g1"]) cmd_run._execute(output, single=True) got = output.getvalue().split("\n")[:-1] # t3 is a depenendency of g1.b but not included assert [". g1.a", ". g1.b"] == got def testProcessRunSingleWithArgs(self, depfile_name): output = StringIO() task_list = tasks_sample() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=task_list, sel_tasks=["t1", "--arg1", "ABC"]) cmd_run._execute(output, single=True) got = output.getvalue().split("\n")[:-1] # t1 is a depenendency of t3 but not included assert [". t1"] == got def testProcessRunEmptyFilter(self, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample(), sel_tasks=[]) cmd_run._execute(output) got = output.getvalue().split("\n")[:-1] assert [] == got class MyReporter(reporter.ConsoleReporter): def get_status(self, task): self.outstream.write('MyReporter.start %s\n' % task.name) class TestCmdRunReporter(object): def testReporterInstance(self, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=[tasks_sample()[0]]) cmd_run._execute(output, reporter=MyReporter(output, {})) got = output.getvalue().split("\n")[:-1] assert 'MyReporter.start t1' == got[0] def testCustomReporter(self, depfile_name): output = StringIO() cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=[tasks_sample()[0]]) cmd_run._execute(output, reporter=MyReporter) got = output.getvalue().split("\n")[:-1] assert 'MyReporter.start t1' == got[0] def testPluginReporter(self, depfile_name): output = StringIO() cmd_run = CmdFactory( Run, backend='dbm', dep_file=depfile_name, task_list=[tasks_sample()[0]], config={'REPORTER':{'my': 'tests.test_cmd_run:MyReporter'}}) cmd_run._execute(output, reporter='my') got = output.getvalue().split("\n")[:-1] assert 'MyReporter.start t1' == got[0] class TestCmdRunOptions(object): def test_outfile(self, depfile_name): cmd_run = CmdFactory(Run, backend='dbm', dep_file=depfile_name, task_list=tasks_sample(), sel_tasks=["g1.a"]) cmd_run._execute('test.out') try: outfile = open('test.out', 'r') got = outfile.read() outfile.close() assert ". g1.a\n" == got finally: if os.path.exists('test.out'): os.remove('test.out') doit-0.36.0/tests/test_cmd_strace.py000066400000000000000000000105261423054503100174170ustar00rootroot00000000000000import os.path from io import StringIO import pytest from doit.cmd_base import TaskLoader2 from doit.exceptions import InvalidCommand from doit.cmdparse import DefaultUpdate from doit.dependency import JSONCodec from doit.task import Task from doit.cmd_strace import Strace from .conftest import CmdFactory @pytest.mark.skipif( "os.system('strace -V') != 0 or sys.platform in ['win32', 'cygwin']") class TestCmdStrace(object): @staticmethod def loader_for_task(task): class MyTaskLoader(TaskLoader2): def load_doit_config(self): return {} def load_tasks(self, cmd, pos_args): return [task] return MyTaskLoader() def test_dep(self, dependency1, depfile_name): output = StringIO() task = Task("tt", ["cat %(dependencies)s"], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Strace, outstream=output) cmd.loader = self.loader_for_task(task) params = DefaultUpdate(dep_file=depfile_name, show_all=False, keep_trace=False, backend='dbm', check_file_uptodate='md5', codec_cls=JSONCodec) result = cmd.execute(params, ['tt']) assert 0 == result got = output.getvalue().split("\n") dep_path = os.path.abspath("tests/data/dependency1") assert "R %s" % dep_path in got[0] def test_opt_show_all(self, dependency1, depfile_name): output = StringIO() task = Task("tt", ["cat %(dependencies)s"], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Strace, outstream=output) cmd.loader = self.loader_for_task(task) params = DefaultUpdate(dep_file=depfile_name, show_all=True, keep_trace=False, backend='dbm', check_file_uptodate='md5', codec_cls=JSONCodec) result = cmd.execute(params, ['tt']) assert 0 == result got = output.getvalue().split("\n") assert "cat" in got[0] def test_opt_keep_trace(self, dependency1, depfile_name): output = StringIO() task = Task("tt", ["cat %(dependencies)s"], file_dep=['tests/data/dependency1']) cmd = CmdFactory(Strace, outstream=output) cmd.loader = self.loader_for_task(task) params = DefaultUpdate(dep_file=depfile_name, show_all=True, keep_trace=True, backend='dbm', check_file_uptodate='md5', codec_cls=JSONCodec) result = cmd.execute(params, ['tt']) assert 0 == result got = output.getvalue().split("\n") assert "cat" in got[0] assert os.path.exists(cmd.TRACE_OUT) os.unlink(cmd.TRACE_OUT) def test_target(self, dependency1, depfile_name): output = StringIO() task = Task("tt", ["touch %(targets)s"], targets=['tests/data/dependency1']) cmd = CmdFactory(Strace, outstream=output) cmd.loader = self.loader_for_task(task) params = DefaultUpdate(dep_file=depfile_name, show_all=False, keep_trace=False, backend='dbm', check_file_uptodate='md5', codec_cls=JSONCodec) result = cmd.execute(params, ['tt']) assert 0 == result got = output.getvalue().split("\n") tgt_path = os.path.abspath("tests/data/dependency1") assert "W %s" % tgt_path in got[0] def test_ignore_python_actions(self, dependency1, depfile_name): output = StringIO() def py_open(): with open(dependency1) as ignore: ignore task = Task("tt", [py_open]) cmd = CmdFactory(Strace, outstream=output) cmd.loader = self.loader_for_task(task) params = DefaultUpdate(dep_file=depfile_name, show_all=False, keep_trace=False, backend='dbm', check_file_uptodate='md5', codec_cls=JSONCodec) result = cmd.execute(params, ['tt']) assert 0 == result def test_invalid_command_args(self): output = StringIO() cmd = CmdFactory(Strace, outstream=output) # fails if number of args != 1 pytest.raises(InvalidCommand, cmd.execute, {}, []) pytest.raises(InvalidCommand, cmd.execute, {}, ['t1', 't2']) doit-0.36.0/tests/test_cmdparse.py000066400000000000000000000305031423054503100171060ustar00rootroot00000000000000import os import pickle import pytest from doit.cmdparse import DefaultUpdate, CmdParseError, CmdOption, CmdParse class TestDefaultUpdate(object): def test(self): du = DefaultUpdate() du.set_default('a', 0) du.set_default('b', 0) assert 0 == du['a'] assert 0 == du['b'] # set b with non-default value du['b'] = 1 # only a is update du.update_defaults({'a':2, 'b':2}) assert 2 == du['a'] assert 1 == du['b'] # default for `a` can be updated again du.update_defaults({'a':3}) assert 3 == du['a'] def test_add_defaults(self): du = DefaultUpdate() du.add_defaults({'a': 0, 'b':1}) du['c'] = 5 du.add_defaults({'a':2, 'c':2}) assert 0 == du['a'] assert 1 == du['b'] assert 5 == du['c'] # http://bugs.python.org/issue826897 def test_pickle(self): du = DefaultUpdate() du.set_default('x', 0) dump = pickle.dumps(du,2) pickle.loads(dump) class TestCmdOption(object): def test_repr(self): opt = CmdOption({'name':'opt1', 'default':'', 'short':'o', 'long':'other'}) assert "CmdOption(" in repr(opt) assert "'name':'opt1'" in repr(opt) assert "'short':'o'" in repr(opt) assert "'long':'other'" in repr(opt) def test_non_required_fields(self): opt1 = CmdOption({'name':'op1', 'default':''}) assert '' == opt1.long def test_invalid_field(self): opt_dict = {'name':'op1', 'default':'', 'non_existent':''} pytest.raises(CmdParseError, CmdOption, opt_dict) def test_missing_field(self): opt_dict = {'name':'op1', 'long':'abc'} pytest.raises(CmdParseError, CmdOption, opt_dict) class TestCmdOption_str2val(object): def test_str2boolean(self): opt = CmdOption({'name':'op1', 'default':'', 'type':bool, 'short':'b', 'long': 'bobo'}) assert True == opt.str2boolean('1') assert True == opt.str2boolean('yes') assert True == opt.str2boolean('Yes') assert True == opt.str2boolean('YES') assert True == opt.str2boolean('true') assert True == opt.str2boolean('on') assert False == opt.str2boolean('0') assert False == opt.str2boolean('false') assert False == opt.str2boolean('no') assert False == opt.str2boolean('off') assert False == opt.str2boolean('OFF') pytest.raises(ValueError, opt.str2boolean, '2') pytest.raises(ValueError, opt.str2boolean, None) pytest.raises(ValueError, opt.str2boolean, 'other') def test_non_string_values_are_not_converted(self): opt = CmdOption({'name':'op1', 'default':'', 'type':bool}) assert False == opt.str2type(False) assert True == opt.str2type(True) assert None == opt.str2type(None) def test_str(self): opt = CmdOption({'name':'op1', 'default':'', 'type':str}) assert 'foo' == opt.str2type('foo') assert 'bar' == opt.str2type('bar') def test_bool(self): opt = CmdOption({'name':'op1', 'default':'', 'type':bool}) assert False == opt.str2type('off') assert True == opt.str2type('on') def test_int(self): opt = CmdOption({'name':'op1', 'default':'', 'type':int}) assert 2 == opt.str2type('2') assert -3 == opt.str2type('-3') def test_list(self): opt = CmdOption({'name':'op1', 'default':'', 'type':list}) assert ['foo'] == opt.str2type('foo') assert [] == opt.str2type('') assert ['foo', 'bar'] == opt.str2type('foo , bar ') def test_invalid_value(self): opt = CmdOption({'name':'op1', 'default':'', 'type':int}) pytest.raises(CmdParseError, opt.str2type, 'not a number') class TestCmdOption_help_param(object): def test_bool_param(self): opt1 = CmdOption({'name':'op1', 'default':'', 'type':bool, 'short':'b', 'long': 'bobo'}) assert '-b, --bobo' == opt1.help_param() def test_non_bool_param(self): opt1 = CmdOption({'name':'op1', 'default':'', 'type':str, 'short':'s', 'long': 'susu'}) assert '-s ARG, --susu=ARG' == opt1.help_param() def test_metavar(self): opt1 = CmdOption({'name':'op1', 'default':'', 'type':str, 'metavar':'VAL', 'short':'s', 'long': 'susu'}) assert '-s VAL, --susu=VAL' == opt1.help_param() def test_no_long(self): opt1 = CmdOption({'name':'op1', 'default':'', 'type':str, 'short':'s'}) assert '-s ARG' == opt1.help_param() opt_bool = {'name': 'flag', 'short':'f', 'long': 'flag', 'inverse':'no-flag', 'type': bool, 'default': False, 'help': 'help for opt1'} opt_rare = {'name': 'rare_bool', 'long': 'rare-bool', 'env_var': 'RARE', 'type': bool, 'default': False, 'help': 'help for opt2',} opt_int = {'name': 'num', 'short':'n', 'long': 'number', 'type': int, 'default': 5, 'help': 'help for opt3'} opt_no = {'name': 'no', 'short':'', 'long': '', 'type': int, 'default': 5, 'help': 'user cant modify me'} opt_append = { 'name': 'list', 'short': 'l', 'long': 'list', 'type': list, 'default': [], 'help': 'use many -l to make a list'} opt_choices_desc = {'name': 'choices', 'short':'c', 'long': 'choice', 'type': str, 'choices': (("yes", "signify affirmative"), ("no","signify negative")), 'default': "yes", 'help': 'User chooses [default %(default)s]'} opt_choices_nodesc = {'name': 'choicesnodesc', 'short':'C', 'long': 'achoice', 'type': str, 'choices': (("yes", ""), ("no", "")), 'default': "no", 'help': 'User chooses [default %(default)s]'} class TestCmdOption_help_doc(object): def test_param(self): opt1 = CmdOption(opt_bool) got = opt1.help_doc() assert '-f, --flag' in got[0] assert 'help for opt1' in got[0] assert '--no-flag' in got[1] assert 2 == len(got) def test_no_doc_param(self): opt1 = CmdOption(opt_no) assert 0 == len(opt1.help_doc()) def test_choices_desc_doc(self): the_opt = CmdOption(opt_choices_desc) doc = the_opt.help_doc()[0] assert 'choices:\n' in doc assert 'yes: signify affirmative' in doc assert 'no: signify negative' in doc def test_choices_nodesc_doc(self): the_opt = CmdOption(opt_choices_nodesc) doc = the_opt.help_doc()[0] assert "choices: no, yes" in doc def test_name_config_env(self): opt1 = CmdOption(opt_rare) got = opt1.help_doc() assert 'config: rare_bool' in got[0] assert 'environ: RARE' in got[0] class TestCommand(object): @pytest.fixture def cmd(self, request): opt_list = (opt_bool, opt_rare, opt_int, opt_no, opt_append, opt_choices_desc, opt_choices_nodesc) options = [CmdOption(o) for o in opt_list] cmd = CmdParse(options) return cmd def test_contains(self, cmd): assert 'flag' in cmd assert 'num' in cmd assert 'xxx' not in cmd def test_getitem(self, cmd): assert cmd['flag'].short == 'f' assert cmd['num'].default == 5 def test_option_list(self, cmd): opt_names = [o.name for o in cmd.options] assert ['flag', 'rare_bool', 'num', 'no', 'list', 'choices', 'choicesnodesc']== opt_names def test_short(self, cmd): assert "fn:l:c:C:" == cmd.get_short(), cmd.get_short() def test_long(self, cmd): longs = ["flag", "no-flag", "rare-bool", "number=", "list=", "choice=", "achoice="] assert longs == cmd.get_long() def test_getOption(self, cmd): # short opt, is_inverse = cmd.get_option('-f') assert (opt_bool['name'], False) == (opt.name, is_inverse) # long opt, is_inverse = cmd.get_option('--rare-bool') assert (opt_rare['name'], False) == (opt.name, is_inverse) # inverse opt, is_inverse = cmd.get_option('--no-flag') assert (opt_bool['name'], True) == (opt.name, is_inverse) # not found opt, is_inverse = cmd.get_option('not-there') assert (None, None) == (opt, is_inverse) opt, is_inverse = cmd.get_option('--list') assert (opt_append['name'], False) == (opt.name, is_inverse) opt, is_inverse = cmd.get_option('--choice') assert (opt_choices_desc['name'], False) == (opt.name, is_inverse) opt, is_inverse = cmd.get_option('--achoice') assert (opt_choices_nodesc['name'], False) == (opt.name, is_inverse) def test_parseDefaults(self, cmd): params, args = cmd.parse([]) assert False == params['flag'] assert 5 == params['num'] assert [] == params['list'] assert "yes" == params['choices'] assert "no" == params['choicesnodesc'] def test_overwrite_defaults(self, cmd): cmd.overwrite_defaults({'num': 9, 'i_dont_exist': 1}) params, args = cmd.parse([]) assert 9 == params['num'] def test_overwrite_defaults_convert_type(self, cmd): cmd.overwrite_defaults({'num': '9', 'list': 'foo, bar', 'flag':'on'}) params, args = cmd.parse([]) assert 9 == params['num'] assert ['foo', 'bar'] == params['list'] assert True == params['flag'] def test_parseShortValues(self, cmd): params, args = cmd.parse(['-n','89','-f', '-l', 'foo', '-l', 'bar', '-c', 'no', '-C', 'yes']) assert True == params['flag'] assert 89 == params['num'] assert ['foo', 'bar'] == params['list'] assert "no" == params['choices'] assert "yes" == params['choicesnodesc'] def test_parseLongValues(self, cmd): params, args = cmd.parse(['--rare-bool','--num','89', '--no-flag', '--list', 'flip', '--list', 'flop', '--choice', 'no', '--achoice', 'yes']) assert True == params['rare_bool'] assert False == params['flag'] assert 89 == params['num'] assert ['flip', 'flop'] == params['list'] assert "no" == params['choices'] assert "yes" == params['choicesnodesc'] def test_parsePositionalArgs(self, cmd): params, args = cmd.parse(['-f','p1','p2', '--sub-arg']) assert ['p1','p2', '--sub-arg'] == args def test_parseError(self, cmd): pytest.raises(CmdParseError, cmd.parse, ['--not-exist-param']) def test_parseWrongType(self, cmd): pytest.raises(CmdParseError, cmd.parse, ['--num','oi']) def test_parseWrongChoice(self, cmd): pytest.raises(CmdParseError, cmd.parse, ['--choice', 'maybe']) def test_env_val(self): opt_foo = { 'name': 'foo', 'long': 'foo', 'type': str, 'env_var': 'FOO', 'default': 'zero' } cmd = CmdParse([CmdOption(opt_foo)]) # get default params, args = cmd.parse([]) assert params['foo'] == 'zero' # get from env os.environ['FOO'] = 'bar' params2, args2 = cmd.parse([]) assert params2['foo'] == 'bar' # command line has precedence params2, args2 = cmd.parse(['--foo', 'XXX']) assert params2['foo'] == 'XXX' def test_env_val_bool(self): opt_foo = { 'name': 'foo', 'long': 'foo', 'type': bool, 'env_var': 'FOO', 'default': False, } cmd = CmdParse([CmdOption(opt_foo)]) # get from env os.environ['FOO'] = '1' params, args = cmd.parse([]) assert params['foo'] == True # get from env os.environ['FOO'] = '0' params, args = cmd.parse([]) assert params['foo'] == False doit-0.36.0/tests/test_control.py000066400000000000000000000711301423054503100167710ustar00rootroot00000000000000from collections import deque import pytest from doit.exceptions import InvalidDodoFile, InvalidCommand from doit.task import Stream, InvalidTask, Task, DelayedLoader from doit.control import TaskControl, TaskDispatcher, ExecNode from doit.control import no_none class TestTaskControlInit(object): def test_addTask(self): t1 = Task("taskX", None) t2 = Task("taskY", None) tc = TaskControl([t1, t2]) assert 2 == len(tc.tasks) def test_targetDependency(self): t1 = Task("taskX", None,[],['intermediate']) t2 = Task("taskY", None,['intermediate'],[]) TaskControl([t1, t2]) assert ['taskX'] == t2.task_dep # 2 tasks can not have the same name def test_addTaskSameName(self): t1 = Task("taskX", None) t2 = Task("taskX", None) pytest.raises(InvalidDodoFile, TaskControl, [t1, t2]) def test_addInvalidTask(self): pytest.raises(InvalidTask, TaskControl, [666]) def test_userErrorTaskDependency(self): tasks = [Task('wrong', None, task_dep=["typo"])] pytest.raises(InvalidTask, TaskControl, tasks) def test_userErrorSetupTask(self): tasks = [Task('wrong', None, setup=["typo"])] pytest.raises(InvalidTask, TaskControl, tasks) def test_sameTarget(self): tasks = [Task('t1',None,[],["fileX"]), Task('t2',None,[],["fileX"])] pytest.raises(InvalidTask, TaskControl, tasks) def test_wild(self): tasks = [Task('t1',None, task_dep=['foo*']), Task('foo4',None,)] TaskControl(tasks) assert 'foo4' in tasks[0].task_dep def test_bug770150_task_dependency_from_target(self): t1 = Task("taskX", None, file_dep=[], targets=['intermediate']) t2 = Task("taskY", None, file_dep=['intermediate'], task_dep=['taskZ']) t3 = Task("taskZ", None) TaskControl([t1, t2, t3]) assert ['taskZ', 'taskX'] == t2.task_dep @pytest.fixture def tasks_sample(): return [ Task("t1", [""], doc="t1 doc string"), Task("t2", [""], doc="t2 doc string"), Task("g1", None, doc="g1 doc string"), Task("g1.a", [""], doc="g1.a doc string", subtask_of='g1'), Task("g1.b", [""], doc="g1.b doc string", subtask_of='g1'), Task("t3", [""], doc="t3 doc string", params=[{'name':'opt1','long':'message','default':''}]) ] class TestTaskControlCmdOptions(object): def testFilter(self, tasks_sample): filter_ = ['t2', 't3'] tc = TaskControl(tasks_sample) assert filter_ == tc._filter_tasks(filter_) def testProcessSelection(self, tasks_sample): filter_ = ['t2', 't3'] tc = TaskControl(tasks_sample) tc.process(filter_) assert filter_ == tc.selected_tasks def testProcessAll(self, tasks_sample): tc = TaskControl(tasks_sample) tc.process(None) assert ['t1', 't2', 'g1', 'g1.a', 'g1.b', 't3'] == tc.selected_tasks def testFilterPattern(self, tasks_sample): tc = TaskControl(tasks_sample) assert ['t1', 'g1', 'g1.a', 'g1.b'] == tc._filter_tasks(['*1*']) def testFilterSubtask(self, tasks_sample): filter_ = ["t1", "g1.b"] tc = TaskControl(tasks_sample) assert filter_ == tc._filter_tasks(filter_) def testFilterTarget(self, tasks_sample): tasks = list(tasks_sample) tasks.append(Task("tX", [""],[],["targetX"])) tc = TaskControl(tasks) assert ['tX'] == tc._filter_tasks(["targetX"]) def test_filter_delayed_subtask(self): t1 = Task("taskX", None) t2 = Task("taskY", None, loader=DelayedLoader(lambda: None)) control = TaskControl([t1, t2]) control._filter_tasks(['taskY:foo']) assert isinstance(t2.loader, DelayedLoader) # sub-task will use same loader, and keep parent basename assert control.tasks['taskY:foo'].loader.basename == 'taskY' assert control.tasks['taskY:foo'].loader is t2.loader def test_filter_delayed_regex_single(self): t1 = Task("taskX", None) t2 = Task("taskY", None, loader=DelayedLoader(lambda: None, target_regex='a.*')) t3 = Task("taskZ", None, loader=DelayedLoader(lambda: None, target_regex='b.*')) t4 = Task("taskW", None, loader=DelayedLoader(lambda: None)) control = TaskControl([t1, t2, t3, t4], auto_delayed_regex=False) selected = control._filter_tasks(['abc']) assert isinstance(t2.loader, DelayedLoader) assert len(selected) == 1 assert selected[0] == '_regex_target_abc:taskY' sel_task = control.tasks['_regex_target_abc:taskY'] assert sel_task.file_dep == {'abc'} assert sel_task.loader.basename == 'taskY' assert sel_task.loader is t2.loader def test_filter_delayed_multi_select(self): t1 = Task("taskX", None) t2 = Task("taskY", None, loader=DelayedLoader(lambda: None, target_regex='a.*')) t3 = Task("taskZ", None, loader=DelayedLoader(lambda: None, target_regex='b.*')) t4 = Task("taskW", None, loader=DelayedLoader(lambda: None)) control = TaskControl([t1, t2, t3, t4], auto_delayed_regex=False) selected = control._filter_tasks(['abc', 'att']) assert isinstance(t2.loader, DelayedLoader) assert len(selected) == 2 assert selected[0] == '_regex_target_abc:taskY' assert selected[1] == '_regex_target_att:taskY' def test_filter_delayed_regex_multiple_match(self): t1 = Task("taskX", None) t2 = Task("taskY", None, loader=DelayedLoader(lambda: None, target_regex='a.*')) t3 = Task("taskZ", None, loader=DelayedLoader(lambda: None, target_regex='ab.')) t4 = Task("taskW", None, loader=DelayedLoader(lambda: None)) control = TaskControl([t1, t2, t3, t4], auto_delayed_regex=False) selected = control._filter_tasks(['abc']) assert len(selected) == 2 assert (sorted(selected) == ['_regex_target_abc:taskY', '_regex_target_abc:taskZ']) assert control.tasks['_regex_target_abc:taskY'].file_dep == {'abc'} assert control.tasks['_regex_target_abc:taskZ'].file_dep == {'abc'} assert (control.tasks['_regex_target_abc:taskY'].loader.basename == t2.name) assert (control.tasks['_regex_target_abc:taskZ'].loader.basename == t3.name) def test_filter_delayed_regex_auto(self): t1 = Task("taskX", None) t2 = Task("taskY", None, loader=DelayedLoader(lambda: None, target_regex='a.*')) t3 = Task("taskZ", None, loader=DelayedLoader(lambda: None)) control = TaskControl([t1, t2, t3], auto_delayed_regex=True) selected = control._filter_tasks(['abc']) assert len(selected) == 2 assert (sorted(selected) == ['_regex_target_abc:taskY', '_regex_target_abc:taskZ']) assert control.tasks['_regex_target_abc:taskY'].file_dep == {'abc'} assert control.tasks['_regex_target_abc:taskZ'].file_dep == {'abc'} assert (control.tasks['_regex_target_abc:taskY'].loader.basename == t2.name) assert (control.tasks['_regex_target_abc:taskZ'].loader.basename == t3.name) # filter a non-existent task raises an error def testFilterWrongName(self, tasks_sample): tc = TaskControl(tasks_sample) pytest.raises(InvalidCommand, tc._filter_tasks, ['no']) def testFilterWrongSubtaskName(self): t1 = Task("taskX", None) t2 = Task("taskY", None) tc = TaskControl([t1, t2]) pytest.raises(InvalidCommand, tc._filter_tasks, ['taskX:no']) def testFilterEmptyList(self, tasks_sample): filter_ = [] tc = TaskControl(tasks_sample) assert filter_ == tc._filter_tasks(filter_) def testOptions(self, tasks_sample): options = ["t3", "--message", "hello option!", "t1"] tc = TaskControl(tasks_sample) assert ['t3', 't1'] == tc._filter_tasks(options) assert "hello option!" == tc.tasks['t3'].options['opt1'] def testPosParam(self, tasks_sample): tasks = list(tasks_sample) tasks.append(Task("tP", [""],[],[], pos_arg='myp')) tc = TaskControl(tasks) args = ["tP", "hello option!", "t1"] assert ['tP',] == tc._filter_tasks(args) assert ["hello option!", "t1"] == tc.tasks['tP'].pos_arg_val class TestExecNode(object): def test_repr(self): node = ExecNode(Task('t1', None), None) assert 't1' in repr(node) def test_ready_select__not_waiting(self): task = Task("t1", None) node = ExecNode(task, None) assert False == node.wait_select def test_parent_status_failure(self): n1 = ExecNode(Task('t1', None), None) n2 = ExecNode(Task('t2', None), None) n1.run_status = 'failure' n2.parent_status(n1) assert [n1] == n2.bad_deps assert [] == n2.ignored_deps def test_parent_status_ignore(self): n1 = ExecNode(Task('t1', None), None) n2 = ExecNode(Task('t2', None), None) n1.run_status = 'ignore' n2.parent_status(n1) assert [] == n2.bad_deps assert [n1] == n2.ignored_deps def test_step(self): def my_gen(): yield 1 yield 2 task = Task("t1", None) node = ExecNode(task, None) node.generator = my_gen() assert 1 == node.step() assert 2 == node.step() assert None == node.step() class TestDecoratorNoNone(object): def test_filtering(self): def my_gen(): yield 1 yield None yield 2 gen = no_none(my_gen) assert [1, 2] == [x for x in gen()] class TestTaskDispatcher_GenNone(object): def test_create(self): tasks = {'t1': Task('t1', None)} td = TaskDispatcher(tasks, [], None) node = td._gen_node(None, 't1') assert isinstance(node, ExecNode) assert node == td.nodes['t1'] def test_already_created(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None) } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') td._gen_node(n1, 't2') assert None == td._gen_node(None, 't1') def test_cyclic(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None) } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(n1, 't2') pytest.raises(InvalidDodoFile, td._gen_node, n2, 't1') class TestTaskDispatcher_node_add_wait_run(object): def test_wait(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n1.wait_run.add('xxx') td._node_add_wait_run(n1, ['t2']) assert 2 == len(n1.wait_run) assert 't2' in n1.wait_run assert not n1.bad_deps assert n1 in n2.waiting_me def test_none(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n2.run_status = 'done' td._node_add_wait_run(n1, ['t2']) assert not n1.wait_run def test_deps_not_ok(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n2.run_status = 'failure' td._node_add_wait_run(n1, ['t2']) assert n1.bad_deps def test_calc_dep_already_executed(self): tasks = {'t1': Task('t1', None, calc_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n2.run_status = 'done' n2.task.values = {'calc_dep': ['t3'], 'task_dep':['t5']} td._node_add_wait_run(n1, ['t2'], calc=True) # n1 is updated with results from t2 assert n1.calc_dep == set(['t2', 't3']) assert n1.task_dep == ['t5'] # n1 doesnt need to wait any calc_dep to be executed assert n1.wait_run_calc == set() class TestTaskDispatcher_add_task(object): def test_no_deps(self): tasks = {'t1': Task('t1', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') assert [tasks['t1']] == list(td._add_task(n1)) def test_task_deps(self): tasks = {'t1': Task('t1', None, task_dep=['t2', 't3']), 't2': Task('t2', None), 't3': Task('t3', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') gen = td._add_task(n1) n2 = next(gen) assert tasks['t2'] == n2.task n3 = next(gen) assert tasks['t3'] == n3.task assert 'wait' == next(gen) tasks['t2'].run_status = 'done' td._update_waiting(n2) tasks['t3'].run_status = 'done' td._update_waiting(n3) assert tasks['t1'] == next(gen) def test_task_deps_already_created(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') assert 'wait' == n1.step() assert 'wait' == n1.step() #tasks['t2'].run_status = 'done' td._update_waiting(n2) assert tasks['t1'] == n1.step() def test_task_deps_no_wait(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n2.run_status = 'done' gen = td._add_task(n1) assert tasks['t1'] == next(gen) def test_calc_dep(self): def calc_intermediate(): return {'file_dep': ['intermediate']} tasks = {'t1': Task('t1', None, calc_dep=['t2']), 't2': Task('t2', [calc_intermediate]), 't3': Task('t3', None, targets=['intermediate']), } td = TaskDispatcher(tasks, {'intermediate': 't3'}, None) n1 = td._gen_node(None, 't1') n2 = n1.step() assert tasks['t2'] == n2.task assert 'wait' == n1.step() # execute t2 to process calc_dep tasks['t2'].execute(Stream(0)) td.nodes['t2'].run_status = 'done' td._update_waiting(n2) n3 = n1.step() assert tasks['t3'] == n3.task assert 'intermediate' in tasks['t1'].file_dep assert 't3' in tasks['t1'].task_dep # t3 was added by calc dep assert 'wait' == n1.step() n3.run_status = 'done' td._update_waiting(n3) assert tasks['t1'] == n1.step() def test_calc_dep_already_executed(self): tasks = {'t1': Task('t1', None, calc_dep=['t2']), 't2': Task('t2', None), 't3': Task('t3', None), } td = TaskDispatcher(tasks, {'intermediate': 't3'}, None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') n2.run_status = 'done' n2.task.values = {'calc_dep': ['t3']} assert 't3' == n1.step().task.name assert set() == n1.wait_run assert set() == n1.wait_run_calc #assert False def test_setup_task__run(self): tasks = {'t1': Task('t1', None, setup=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') gen = td._add_task(n1) assert tasks['t1'] == next(gen) # first time (just select) assert 'wait' == next(gen) # wait for select result n1.run_status = 'run' assert tasks['t2'] == next(gen).task # send setup task assert 'wait' == next(gen) assert tasks['t1'] == next(gen) # second time def test_delayed_creation(self): def creator(): yield Task('foo', None, loader=DelayedLoader(lambda : None)) delayed_loader = DelayedLoader(creator, executed='t2') tasks = {'t1': Task('t1', None, loader=delayed_loader), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') gen = td._add_task(n1) # first returned node is `t2` because it is an implicit task_dep n2 = next(gen) assert n2.task.name == 't2' # wait until t2 is finished n3 = next(gen) assert n3 == 'wait' # after t2 is done, generator is reseted td._update_waiting(n2) n4 = next(gen) assert n4 == "reset generator" # recursive loader is preserved assert isinstance(td.tasks['foo'].loader, DelayedLoader) pytest.raises(AssertionError, next, gen) def test_delayed_creation_sub_task(self): # usually a repeated loader is replaced by the real task # when it is first executed, the problem arises when the # the expected task is not actually created def creator(): yield Task('t1:foo', None) yield Task('t1:bar', None) delayed_loader = DelayedLoader(creator, executed='t2') tasks = { 't1': Task('t1', None, loader=delayed_loader), 't2': Task('t2', None),} # simulate a sub-task from delayed created added to task_list tasks['t1:foo'] = Task('t1:foo', None, loader=delayed_loader) tasks['t1:xxx'] = Task('t1:xxx', None, loader=delayed_loader) delayed_loader.basename = 't1' td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1:foo') gen = td._add_task(n1) # first returned node is `t2` because it is an implicit task_dep n1b = next(gen) assert n1b.task.name == 't2' # wait until t2 is finished n1c = next(gen) assert n1c == 'wait' # after t2 is done, generator is reseted n1b.run_status = 'successful' td._update_waiting(n1b) n1d = next(gen) assert n1d == "reset generator" assert 't1:foo' in td.tasks assert 't1:bar' in td.tasks # finish with t1:foo gen2 = td._add_task(n1) n1.reset_task(td.tasks[n1.task.name], gen2) n2 = next(gen2) assert n2.name == 't1:foo' pytest.raises(StopIteration, next, gen2) # try non-existent t1:xxx n3 = td._gen_node(None, 't1:xxx') gen3 = td._add_task(n3) # ? should raise a runtime error? assert next(gen3) == 'reset generator' def test_delayed_creation_target_regex(self): def creator(): yield Task('foo', None, targets=['tgt1']) delayed_loader = DelayedLoader(creator, executed='t2', target_regex='tgt1') tasks = { 't1': Task('t1', None, loader=delayed_loader), 't2': Task('t2', None), } tc = TaskControl(list(tasks.values())) selection = tc._filter_tasks(['tgt1']) assert ['_regex_target_tgt1:t1'] == selection td = TaskDispatcher(tc.tasks, tc.targets, selection) n1 = td._gen_node(None, '_regex_target_tgt1:t1') gen = td._add_task(n1) # first returned node is `t2` because it is an implicit task_dep n2 = next(gen) assert n2.task.name == 't2' # wait until t2 is finished n3 = next(gen) assert n3 == 'wait' # after t2 is done, generator is reseted n2.run_status = 'done' td._update_waiting(n2) n4 = next(gen) assert n4 == "reset generator" # manually reset generator n1.reset_task(td.tasks[n1.task.name], td._add_task(n1)) # get the delayed created task gen2 = n1.generator # n1 generator was reset / replaced # get t1 because of its target was a file_dep of _regex_target_tgt1 n5 = next(gen2) assert n5.task.name == 'foo' # get internal created task n5.run_status = 'done' td._update_waiting(n5) n6 = next(gen2) assert n6.name == '_regex_target_tgt1:t1' # file_dep is removed because foo might not be task # that creates this task (support for multi regex matches) assert n6.file_dep == {} def test_regex_group_already_created(self): # this is required to avoid loading more tasks than required, GH-#60 def creator1(): yield Task('foo1', None, targets=['tgt1']) delayed_loader1 = DelayedLoader(creator1, target_regex='tgt.*') def creator2(): # pragma: no cover yield Task('foo2', None, targets=['tgt2']) delayed_loader2 = DelayedLoader(creator2, target_regex='tgt.*') t1 = Task('t1', None, loader=delayed_loader1) t2 = Task('t2', None, loader=delayed_loader2) tc = TaskControl([t1, t2]) selection = tc._filter_tasks(['tgt1']) assert ['_regex_target_tgt1:t1', '_regex_target_tgt1:t2'] == selection td = TaskDispatcher(tc.tasks, tc.targets, selection) n1 = td._gen_node(None, '_regex_target_tgt1:t1') gen = td._add_task(n1) # delayed loader executed, so generator is reset n1b = next(gen) assert n1b == "reset generator" # manually reset generator n1.reset_task(td.tasks[n1.task.name], td._add_task(n1)) # get the delayed created task gen1b = n1.generator # n1 generator was reset / replaced # get t1 because of its target was a file_dep of _regex_target_tgt1 n1c = next(gen1b) assert n1c.task.name == 'foo1' # get internal created task n1c.run_status = 'done' td._update_waiting(n1c) n1d = next(gen1b) assert n1d.name == '_regex_target_tgt1:t1' ## go for second selected task n2 = td._gen_node(None, '_regex_target_tgt1:t2') gen2 = td._add_task(n2) # loader is not executed because target t1 was already found pytest.raises(StopIteration, next, gen2) def test_regex_not_found(self): def creator1(): yield Task('foo1', None, targets=['tgt1']) delayed_loader1 = DelayedLoader(creator1, target_regex='tgt.*') t1 = Task('t1', None, loader=delayed_loader1) tc = TaskControl([t1]) selection = tc._filter_tasks(['tgt666']) assert ['_regex_target_tgt666:t1'] == selection td = TaskDispatcher(tc.tasks, tc.targets, selection) n1 = td._gen_node(None, '_regex_target_tgt666:t1') gen = td._add_task(n1) # target not found after generating all tasks from regex group pytest.raises(InvalidCommand, next, gen) class TestTaskDispatcher_get_next_node(object): def test_none(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) assert None == td._get_next_node([], []) def test_ready(self): tasks = {'t1': Task('t1', None), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') ready = deque([n1]) assert n1 == td._get_next_node(ready, ['t2']) assert 0 == len(ready) def test_to_run(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) to_run = ['t2', 't1'] td._gen_node(None, 't1') # t1 was already created got = td._get_next_node([], to_run) assert isinstance(got, ExecNode) assert 't2' == got.task.name assert [] == to_run def test_to_run_none(self): tasks = {'t1': Task('t1', None), } td = TaskDispatcher(tasks, [], None) td._gen_node(None, 't1') # t1 was already created to_run = ['t1'] assert None == td._get_next_node([], to_run) assert [] == to_run class TestTaskDispatcher_update_waiting(object): def test_wait_select(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n2 = td._gen_node(None, 't2') n2.wait_select = True n2.run_status = 'run' td.waiting.add(n2) td._update_waiting(n2) assert False == n2.wait_select assert deque([n2]) == td.ready def test_wait_run(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') td._node_add_wait_run(n1, ['t2']) n2.run_status = 'done' td.waiting.add(n1) td._update_waiting(n2) assert not n1.bad_deps assert deque([n1]) == td.ready assert 0 == len(td.waiting) def test_wait_run_deps_not_ok(self): tasks = {'t1': Task('t1', None, task_dep=['t2']), 't2': Task('t2', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n2 = td._gen_node(None, 't2') td._node_add_wait_run(n1, ['t2']) n2.run_status = 'failure' td.waiting.add(n1) td._update_waiting(n2) assert n1.bad_deps assert deque([n1]) == td.ready assert 0 == len(td.waiting) def test_waiting_node_updated(self): tasks = {'t1': Task('t1', None, calc_dep=['t2'], task_dep=['t4']), 't2': Task('t2', None), 't3': Task('t3', None), 't4': Task('t4', None), } td = TaskDispatcher(tasks, [], None) n1 = td._gen_node(None, 't1') n1_gen = td._add_task(n1) n2 = next(n1_gen) assert 't2' == n2.task.name assert 't4' == next(n1_gen).task.name assert 'wait' == next(n1_gen) assert set() == n1.calc_dep assert td.waiting == set() n2.run_status = 'done' n2.task.values = {'calc_dep': ['t2', 't3'], 'task_dep':['t5']} assert n1.calc_dep == set() assert n1.task_dep == [] td._update_waiting(n2) assert n1.calc_dep == set(['t3']) assert n1.task_dep == ['t5'] class TestTaskDispatcher_dispatcher_generator(object): def test_normal(self): tasks = [Task("t1", None, task_dep=["t2"]), Task("t2", None,)] control = TaskControl(tasks) control.process(['t1']) gen = control.task_dispatcher().generator n2 = next(gen) assert tasks[1] == n2.task assert "hold on" == next(gen) assert "hold on" == next(gen) # hold until t2 is done assert tasks[0] == gen.send(n2).task pytest.raises(StopIteration, lambda gen: next(gen), gen) def test_delayed_creation(self): def creator(): yield {'name': 'foo1', 'actions': None, 'file_dep': ['bar']} yield {'name': 'foo2', 'actions': None, 'targets': ['bar']} delayed_loader = DelayedLoader(creator, executed='t2') tasks = [Task('t0', None, task_dep=['t1']), Task('t1', None, loader=delayed_loader), Task('t2', None)] control = TaskControl(tasks) control.process(['t0']) disp = control.task_dispatcher() gen = disp.generator nt2 = next(gen) assert nt2.task.name == "t2" # wait for t2 to be executed assert "hold on" == next(gen) assert "hold on" == next(gen) # hold until t2 is done # delayed creation of tasks for t1 does not mess existing info assert disp.nodes['t1'].waiting_me == set([disp.nodes['t0']]) nf2 = gen.send(nt2) assert disp.nodes['t1'].waiting_me == set([disp.nodes['t0']]) assert nf2.task.name == "t1:foo2" nf1 = gen.send(nf2) assert nf1.task.name == "t1:foo1" assert nf1.task.task_dep == ['t1:foo2'] # implicit dep added nt1 = gen.send(nf1) assert nt1.task.name == "t1" nt0 = gen.send(nt1) assert nt0.task.name == "t0" pytest.raises(StopIteration, lambda gen: next(gen), gen) doit-0.36.0/tests/test_dependency.py000066400000000000000000000607701423054503100174370ustar00rootroot00000000000000import os import time from sys import executable import pytest from doit.task import Task from doit.dependency import get_md5, get_file_md5 from doit.dependency import DbmDB, JsonDB, SqliteDB, Dependency from doit.dependency import DatabaseException, UptodateCalculator from doit.dependency import FileChangedChecker, MD5Checker, TimestampChecker from doit.dependency import DependencyStatus from .conftest import get_abspath, dep_manager_fixture # path to test folder TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) def test_unicode_md5(): data = "我" # no exception is raised assert get_md5(data) def test_md5(): filePath = os.path.join(os.path.dirname(__file__), "sample_md5.txt") # result got using command line md5sum, with different line-endings # to deal with different GIT configurations: expected_lf = "45d1503cb985898ab5bd8e58973007dd" expected_crlf = "cf7b48b2fec3b581b135f7c9a1f7ae04" assert get_file_md5(filePath) in {expected_lf, expected_crlf} def test_sqlite_import(): """ Checks that SQLite module is not imported until the SQLite class is instantiated """ from doit import dependency assert not hasattr(dependency, 'sqlite3') #### # dependencies are files only (not other tasks). # # whenever a task has a dependency the runner checks if this dependency # was modified since last successful run. if not the task is skipped. # since more than one task might have the same dependency, and the tasks # might have different results (success/failure). the signature is associated # not only with the file, but also with the task. # # save in db (task - dependency - (timestamp, size, signature)) # taskId_dependency => signature(dependency) # taskId is md5(CmdTask.task) # test parametrization, execute tests for all DB backends. # create a separate fixture to be used only by this module # because only here it is required to test with all backends @pytest.fixture(params=[JsonDB, DbmDB, SqliteDB]) def pdep_manager(request, tmp_path_factory): return dep_manager_fixture(request, request.param, tmp_path_factory) # FIXME there was major refactor breaking classes from dependency, # unit-tests could be more specific to base classes. class TestDependencyDb(object): # adding a new value to the DB def test_get_set(self, pdep_manager): pdep_manager._set("taskId_X", "dependency_A", "da_md5") value = pdep_manager._get("taskId_X", "dependency_A") assert "da_md5" == value, value def test_get_set_unicode_name(self, pdep_manager): pdep_manager._set("taskId_我", "dependency_A", "da_md5") value = pdep_manager._get("taskId_我", "dependency_A") assert "da_md5" == value, value # def test_dump(self, pdep_manager): # save and close db pdep_manager._set("taskId_X", "dependency_A", "da_md5") pdep_manager.close() # open it again and check the value d2 = Dependency(pdep_manager.db_class, pdep_manager.name) value = d2._get("taskId_X", "dependency_A") assert "da_md5" == value, value d2.close() def test_corrupted_file(self, pdep_manager): if pdep_manager.whichdb is None: # pragma: no cover pytest.skip('dumbdbm too dumb to detect db corruption') # create some corrupted files for name_ext in pdep_manager.name_ext: full_name = pdep_manager.name + name_ext fd = open(full_name, 'w') fd.write("""{"x": y}""") fd.close() pytest.raises(DatabaseException, Dependency, pdep_manager.db_class, pdep_manager.name) def test_corrupted_file_unrecognized_excep(self, monkeypatch, pdep_manager): if pdep_manager.db_class is not DbmDB: pytest.skip('test doesnt apply to non DBM DB') if pdep_manager.whichdb is None: # pragma: no cover pytest.skip('dumbdbm too dumb to detect db corruption') # create some corrupted files for name_ext in pdep_manager.name_ext: full_name = pdep_manager.name + name_ext fd = open(full_name, 'w') fd.write("""{"x": y}""") fd.close() monkeypatch.setattr(DbmDB, 'DBM_CONTENT_ERROR_MSG', 'xxx') pytest.raises(DatabaseException, Dependency, pdep_manager.db_class, pdep_manager.name) # _get must return None if entry doesnt exist. def test_getNonExistent(self, pdep_manager): assert pdep_manager._get("taskId_X", "dependency_A") == None def test_in(self, pdep_manager): pdep_manager._set("taskId_ZZZ", "dep_1", "12") assert pdep_manager._in("taskId_ZZZ") assert not pdep_manager._in("taskId_hohoho") def test_remove(self, pdep_manager): pdep_manager._set("taskId_ZZZ", "dep_1", "12") pdep_manager._set("taskId_ZZZ", "dep_2", "13") pdep_manager._set("taskId_YYY", "dep_1", "14") pdep_manager.remove("taskId_ZZZ") assert None == pdep_manager._get("taskId_ZZZ", "dep_1") assert None == pdep_manager._get("taskId_ZZZ", "dep_2") assert "14" == pdep_manager._get("taskId_YYY", "dep_1") # special test for DBM backend and "dirty"/caching mechanism def test_remove_from_non_empty_file(self, pdep_manager): # 1 - put 2 tasks of file pdep_manager._set("taskId_XXX", "dep_1", "x") pdep_manager._set("taskId_YYY", "dep_1", "x") pdep_manager.close() # 2 - re-open and remove one task reopened = Dependency(pdep_manager.db_class, pdep_manager.name) reopened.remove("taskId_YYY") reopened.close() # 3 - re-open again and check task was really removed reopened2 = Dependency(pdep_manager.db_class, pdep_manager.name) assert reopened2._in("taskId_XXX") assert not reopened2._in("taskId_YYY") reopened2.close() def test_remove_all(self, pdep_manager): pdep_manager._set("taskId_ZZZ", "dep_1", "12") pdep_manager._set("taskId_ZZZ", "dep_2", "13") pdep_manager._set("taskId_YYY", "dep_1", "14") pdep_manager.remove_all() assert None == pdep_manager._get("taskId_ZZZ", "dep_1") assert None == pdep_manager._get("taskId_ZZZ", "dep_2") assert None == pdep_manager._get("taskId_YYY", "dep_1") class TestSaveSuccess(object): def test_save_result(self, pdep_manager): t1 = Task('t_name', None) t1.result = "result" pdep_manager.save_success(t1) assert get_md5("result") == pdep_manager._get(t1.name, "result:") assert get_md5("result") == pdep_manager.get_result(t1.name) def test_save_result_hash(self, pdep_manager): t1 = Task('t_name', None) t1.result = "result" pdep_manager.save_success(t1, result_hash='abc') assert 'abc' == pdep_manager._get(t1.name, "result:") def test_save_resultNone(self, pdep_manager): t1 = Task('t_name', None) pdep_manager.save_success(t1) assert None is pdep_manager._get(t1.name, "result:") def test_save_result_dict(self, pdep_manager): t1 = Task('t_name', None) t1.result = {'d': "result"} pdep_manager.save_success(t1) assert {'d': "result"} == pdep_manager._get(t1.name, "result:") def test_save_file_md5(self, pdep_manager): # create a test dependency file filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("i am the first dependency ever for doit") ff.close() # save it t1 = Task("taskId_X", None, [filePath]) pdep_manager.save_success(t1) expected = "a1bb792202ce163b4f0d17cb264c04e1" value = pdep_manager._get("taskId_X", filePath) assert os.path.getmtime(filePath) == value[0] # timestamp assert 39 == value[1] # size assert expected == value[2] # MD5 def test_save_skip(self, pdep_manager, monkeypatch): filePath = get_abspath("data/dependency1") t1 = Task("taskId_X", None, [filePath]) pdep_manager._set(t1.name, filePath, (345, 0, "fake")) monkeypatch.setattr(os.path, 'getmtime', lambda x: 345) # save but md5 is not modified pdep_manager.save_success(t1) got = pdep_manager._get("taskId_X", filePath) assert "fake" == got[2] def test_save_files(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() filePath2 = get_abspath("data/dependency2") ff = open(filePath2, "w") ff.write("part2") ff.close() assert pdep_manager._get("taskId_X", filePath) is None assert pdep_manager._get("taskId_X", filePath2) is None t1 = Task("taskId_X", None, [filePath, filePath2]) pdep_manager.save_success(t1) assert pdep_manager._get("taskId_X", filePath) is not None assert pdep_manager._get("taskId_X", filePath2) is not None assert set(pdep_manager._get("taskId_X", 'deps:')) == t1.file_dep def test_save_values(self, pdep_manager): t1 = Task('t1', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) assert {'x':5, 'y':10} == pdep_manager._get("t1", "_values_:") class TestGetValue(object): def test_all_values(self, pdep_manager): t1 = Task('t1', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) assert {'x':5, 'y':10} == pdep_manager.get_values('t1') def test_ok(self, pdep_manager): t1 = Task('t1', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) assert 5 == pdep_manager.get_value('t1', 'x') def test_ok_dot_on_task_name(self, pdep_manager): t1 = Task('t1:a.ext', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) assert 5 == pdep_manager.get_value('t1:a.ext', 'x') def test_invalid_taskid(self, pdep_manager): t1 = Task('t1', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) pytest.raises(Exception, pdep_manager.get_value, 'nonono', 'x') def test_invalid_key(self, pdep_manager): t1 = Task('t1', None) t1.values = {'x':5, 'y':10} pdep_manager.save_success(t1) pytest.raises(Exception, pdep_manager.get_value, 't1', 'z') class TestRemoveSuccess(object): def test_save_result(self, pdep_manager): t1 = Task('t_name', None) t1.result = "result" pdep_manager.save_success(t1) assert get_md5("result") == pdep_manager._get(t1.name, "result:") pdep_manager.remove_success(t1) assert None is pdep_manager._get(t1.name, "result:") class TestIgnore(object): def test_save_result(self, pdep_manager): t1 = Task('t_name', None) pdep_manager.ignore(t1) assert '1' == pdep_manager._get(t1.name, "ignore:") class TestMD5Checker(object): def test_timestamp(self, dependency1): checker = MD5Checker() state = checker.get_state(dependency1, None) state2 = (state[0], state[1]+1, '') file_stat = os.stat(dependency1) # dep considered the same as long as timestamp is unchanged assert not checker.check_modified(dependency1, file_stat, state2) def test_size(self, dependency1): checker = MD5Checker() state = checker.get_state(dependency1, None) state2 = (state[0]+1, state[1]+1, state[2]) file_stat = os.stat(dependency1) # if size changed for sure modified (md5 is not checked) assert checker.check_modified(dependency1, file_stat, state2) def test_md5(self, dependency1): checker = MD5Checker() state = checker.get_state(dependency1, None) file_stat = os.stat(dependency1) # same size and md5 state2 = (state[0]+1, state[1], state[2]) assert not checker.check_modified(dependency1, file_stat, state2) # same size, different md5 state3 = (state[0]+1, state[1], 'not me') assert checker.check_modified(dependency1, file_stat, state3) class TestCustomChecker(object): def test_not_implemented(self, dependency1): class MyChecker(FileChangedChecker): pass checker = MyChecker() pytest.raises(NotImplementedError, checker.get_state, None, None) pytest.raises(NotImplementedError, checker.check_modified, None, None, None) class TestTimestampChecker(object): def test_timestamp(self, dependency1): checker = TimestampChecker() state = checker.get_state(dependency1, None) file_stat = os.stat(dependency1) assert not checker.check_modified(dependency1, file_stat, state) assert checker.check_modified(dependency1, file_stat, state+1) class TestDependencyStatus(object): def test_add_reason(self): result = DependencyStatus(True) assert 'up-to-date' == result.status assert not result.add_reason('changed_file_dep', 'f1') assert 'run' == result.status assert not result.add_reason('changed_file_dep', 'f2') assert ['f1', 'f2'] == result.reasons['changed_file_dep'] def test_add_reason_error(self): result = DependencyStatus(True) assert 'up-to-date' == result.status assert not result.add_reason('missing_file_dep', 'f1', 'error') assert 'error' == result.status assert ['f1'] == result.reasons['missing_file_dep'] def test_set_reason(self): result = DependencyStatus(True) assert 'up-to-date' == result.status assert not result.set_reason('has_no_dependencies', True) assert 'run' == result.status assert True == result.reasons['has_no_dependencies'] def test_no_log(self): result = DependencyStatus(False) assert 'up-to-date' == result.status assert result.set_reason('has_no_dependencies', True) assert 'run' == result.status def test_get_error_message(self): result = DependencyStatus(False) assert None == result.get_error_message() result.error_reason = 'foo xxx' assert 'foo xxx' == result.get_error_message() class TestGetStatus(object): def test_ignore(self, pdep_manager): t1 = Task("t1", None) # before ignore assert not pdep_manager.status_is_ignore(t1) # after ignote pdep_manager.ignore(t1) assert pdep_manager.status_is_ignore(t1) def test_fileDependencies(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() dependencies = [filePath] t1 = Task("t1", None, dependencies) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert dependencies == t1.dep_changed # second time no pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed # FIXME - mock timestamp time.sleep(1) # required otherwise timestamp is not modified! # a small change on the file ff = open(filePath, "a") ff.write(" part2") ff.close() # execute again assert 'run' == pdep_manager.get_status(t1, {}).status assert dependencies == t1.dep_changed def test_fileDependencies_changed(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() filePath2 = get_abspath("data/dependency2") ff = open(filePath, "w") ff.write("part1") ff.close() dependencies = [filePath, filePath2] t1 = Task("t1", None, dependencies) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert sorted(dependencies) == sorted(t1.dep_changed) # second time no pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed # remove dependency filePath2 t1 = Task("t1", None, [filePath]) # execute again assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed def test_fileDependencies_changed_get_log(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() filePath2 = get_abspath("data/dependency2") ff = open(filePath, "w") ff.write("part1") ff.close() t1 = Task("t1", None, [filePath]) # first time execute result = pdep_manager.get_status(t1, {}, get_log=True) assert 'run' == result.status assert [filePath] == t1.dep_changed pdep_manager.save_success(t1) # second time t1b = Task("t1", None, [filePath2]) result = pdep_manager.get_status(t1b, {}, get_log=True) assert 'run' == result.status assert [filePath2] == t1b.dep_changed assert [filePath] == result.reasons['removed_file_dep'] assert [filePath2] == result.reasons['added_file_dep'] def test_file_dependency_not_exist(self, pdep_manager): filePath = get_abspath("data/dependency_not_exist") t1 = Task("t1", None, [filePath]) assert 'error' == pdep_manager.get_status(t1, {}).status def test_change_checker(self, pdep_manager, dependency1): t1 = Task("taskId_X", None, [dependency1]) pdep_manager.checker = TimestampChecker() pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status # change of checker force `run` again pdep_manager.checker = MD5Checker() assert 'run' == pdep_manager.get_status(t1, {}).status pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status # if there is no dependency the task is always executed def test_noDependency(self, pdep_manager): t1 = Task("t1", None) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed # second too pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed def test_UptodateFalse(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() t1 = Task("t1", None, file_dep=[filePath], uptodate=[False]) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed # second time execute too pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed def test_UptodateTrue(self, pdep_manager): t1 = Task("t1", None, uptodate=[True]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateNone(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() t1 = Task("t1", None, file_dep=[filePath], uptodate=[None]) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert [filePath] == t1.dep_changed # second time execute too pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateFunction_True(self, pdep_manager): def check(task, values): assert task.name == 't1' return True t1 = Task("t1", None, uptodate=[check]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateFunction_False(self, pdep_manager): filePath = get_abspath("data/dependency1") ff = open(filePath, "w") ff.write("part1") ff.close() def check(task, values): return False t1 = Task("t1", None, file_dep=[filePath], uptodate=[check]) # first time execute assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed # second time execute too pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed def test_UptodateFunction_without_args_True(self, pdep_manager): def check(): return True t1 = Task("t1", None, uptodate=[check]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_uptodate_call_all_even_if_some_False(self, pdep_manager): checks = [] def check(): checks.append(1) return False t1 = Task("t1", None, uptodate=[check, check]) assert 'run' == pdep_manager.get_status(t1, {}).status assert 2 == len(checks) def test_UptodateFunction_extra_args_True(self, pdep_manager): def check(task, values, control): assert task.name == 't1' return control > 30 t1 = Task("t1", None, uptodate=[(check, [34])]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateCallable_True(self, pdep_manager): class MyChecker(object): def __call__(self, task, values): assert task.name == 't1' return True t1 = Task("t1", None, uptodate=[MyChecker()]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateMethod_True(self, pdep_manager): class MyChecker(object): def check(self, task, values): assert task.name == 't1' return True t1 = Task("t1", None, uptodate=[MyChecker().check]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateCallable_added_attributes(self, pdep_manager): task_dict = "fake dict" class My_uptodate(UptodateCalculator): def __call__(self, task, values): # attributes were added to object before call'ing it assert task_dict == self.tasks_dict assert None == self.get_val('t1', None) return True check = My_uptodate() t1 = Task("t1", None, uptodate=[check]) assert 'up-to-date' == pdep_manager.get_status(t1, task_dict).status def test_UptodateCommand_True(self, pdep_manager): t1 = Task("t1", None, uptodate=[PROGRAM]) pdep_manager.save_success(t1) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status def test_UptodateCommand_False(self, pdep_manager): t1 = Task("t1", None, uptodate=[PROGRAM + ' please fail']) pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status # if target file does not exist, task is outdated. def test_targets_notThere(self, pdep_manager, dependency1): target = get_abspath("data/target") if os.path.exists(target): os.remove(target) t1 = Task("task x", None, [dependency1], [target]) pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status assert [dependency1] == t1.dep_changed def test_targets(self, pdep_manager, dependency1): filePath = get_abspath("data/target") ff = open(filePath, "w") ff.write("part1") ff.close() deps = [dependency1] targets = [filePath] t1 = Task("task X", None, deps, targets) pdep_manager.save_success(t1) # up-to-date because target exist assert 'up-to-date' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed def test_targetFolder(self, pdep_manager, dependency1): # folder not there. task is not up-to-date deps = [dependency1] folderPath = get_abspath("data/target-folder") if os.path.exists(folderPath): os.rmdir(folderPath) t1 = Task("task x", None, deps, [folderPath]) pdep_manager.save_success(t1) assert 'run' == pdep_manager.get_status(t1, {}).status assert deps == t1.dep_changed # create folder. task is up-to-date os.mkdir(folderPath) assert 'up-to-date' == pdep_manager.get_status(t1, {}).status assert [] == t1.dep_changed doit-0.36.0/tests/test_doit_cmd.py000066400000000000000000000162401423054503100170740ustar00rootroot00000000000000import os from unittest.mock import Mock import tempfile import shutil import pytest from doit.exceptions import InvalidCommand from doit.cmd_run import Run from doit.cmd_list import List from doit import doit_cmd def cmd_main(args): main = doit_cmd.DoitMain() main.BIN_NAME = 'doit' return main.run(args) class TestRun(object): def test_version(self, capsys): cmd_main(["--version"]) out, err = capsys.readouterr() assert "lib" in out def test_usage(self, capsys): cmd_main(["--help"]) out, err = capsys.readouterr() assert "doit list" in out def test_run_is_default(self, monkeypatch): mock_run = Mock() monkeypatch.setattr(Run, "execute", mock_run) cmd_main([]) assert 1 == mock_run.call_count def test_run_other_subcommand(self, monkeypatch): mock_list = Mock() monkeypatch.setattr(List, "execute", mock_list) cmd_main(["list"]) assert 1 == mock_list.call_count def test_cmdline_vars(self, monkeypatch): mock_run = Mock() monkeypatch.setattr(Run, "execute", mock_run) cmd_main(['x=1', 'y=abc']) assert '1' == doit_cmd.get_var('x') assert 'abc' == doit_cmd.get_var('y') assert None is doit_cmd.get_var('z') def test_cmdline_novars(self, monkeypatch): mock_run = Mock() monkeypatch.setattr(Run, "execute", mock_run) cmd_main(['x=1']) # Simulate the variable below not being initialized by a subprocess on # Windows. See https://github.com/pydoit/doit/issues/164. doit_cmd._CMDLINE_VARS = None assert None is doit_cmd.get_var('x') def test_cmdline_vars_not_opts(self, monkeypatch): mock_run = Mock() monkeypatch.setattr(Run, "execute", mock_run) cmd_main(['--z=5']) assert None == doit_cmd.get_var('--z') def test_cmdline_loader_option_before_cmd_name(self, monkeypatch): mock_list = Mock() monkeypatch.setattr(List, "execute", mock_list) cmd_main(['-k', 'list', '--all']) assert mock_list.called params, args = mock_list.call_args[0] assert params['subtasks'] == True assert params['seek_file'] == True assert args == [] def test_cmdline_loader_option_mixed(self, monkeypatch): mock_run = Mock() monkeypatch.setattr(Run, "execute", mock_run) cmd_main(['-c', '-k', 'lala']) assert mock_run.called params, args = mock_run.call_args[0] assert params['continue'] == True assert params['seek_file'] == True assert args == ['lala'] def test_task_loader_has_cmd_list(self, monkeypatch): cmd_names = [] def save_cmd_names(self, params, args): cmd_names.extend(self.loader.cmd_names) monkeypatch.setattr(Run, "execute", save_cmd_names) cmd_main([]) assert 'list' in cmd_names def test_extra_config(self, monkeypatch, depfile_name): outfile_val = [] def monkey_run(self, opt_values, pos_args): outfile_val.append(opt_values['outfile']) monkeypatch.setattr(Run, "execute", monkey_run) extra_config = { 'outfile': 'foo.txt', 'dep_file': depfile_name, } doit_cmd.DoitMain(extra_config={'GLOBAL': extra_config}).run([]) assert outfile_val[0] == 'foo.txt' class TestErrors(object): def test_interrupt(self, monkeypatch): def my_raise(*args): raise KeyboardInterrupt() mock_cmd = Mock(side_effect=my_raise) monkeypatch.setattr(Run, "execute", mock_cmd) pytest.raises(KeyboardInterrupt, cmd_main, []) def test_user_error(self, capsys, monkeypatch): mock_cmd = Mock(side_effect=InvalidCommand) monkeypatch.setattr(Run, "execute", mock_cmd) got = cmd_main([]) assert 3 == got out, err = capsys.readouterr() assert "ERROR" in err def test_internal_error(self, capsys, monkeypatch): mock_cmd = Mock(side_effect=Exception) monkeypatch.setattr(Run, "execute", mock_cmd) got = cmd_main([]) assert 3 == got out, err = capsys.readouterr() # traceback from Exception (this case code from mock lib) assert "mock.py" in err class TestConfig(object): def test_no_ini_config_file(self): main = doit_cmd.DoitMain(config_filenames=()) main.run(['--version']) def test_load_plugins_command(self): config_filename = os.path.join(os.path.dirname(__file__), 'sample.cfg') main = doit_cmd.DoitMain(config_filenames=config_filename) assert 1 == len(main.config['COMMAND']) # test loaded plugin command is actually used with plugin name assert 'foo' in main.get_cmds() def test_merge_api_ini_config(self): config_filename = os.path.join(os.path.dirname(__file__), 'sample.cfg') api_config = {'GLOBAL': {'opty':'10', 'optz':'10'}} main = doit_cmd.DoitMain(config_filenames=config_filename, extra_config=api_config) assert 1 == len(main.config['COMMAND']) # test loaded plugin command is actually used with plugin name assert 'foo' in main.get_cmds() # INI has higher preference the api_config assert main.config['GLOBAL'] == {'optx':'6', 'opty':'7', 'optz':'10'} def test_execute_command_plugin(self, capsys): config_filename = os.path.join(os.path.dirname(__file__), 'sample.cfg') main = doit_cmd.DoitMain(config_filenames=config_filename) main.run(['foo']) got = capsys.readouterr()[0] assert got == 'this command does nothing!\n' # TOML def test_merge_api_toml_config(self): config_filename = os.path.join(os.path.dirname(__file__), 'sample.toml') api_config = {'GLOBAL': {'opty':'10', 'optz':'10'}} main = doit_cmd.DoitMain(config_filenames=config_filename, extra_config=api_config) assert 1 == len(main.config['COMMAND']) # test loaded plugin command is actually used with plugin name assert 'foo' in main.get_cmds() # INI has higher preference the api_config assert main.config['GLOBAL'] == {'optx':'6', 'opty':'7', 'optz':'10'} assert main.config['foo'] == {'nval': 33} assert main.config['task:bar'] == {'opt': "baz"} def test_find_pyproject_toml_config(self): config_filename = os.path.join(os.path.dirname(__file__), 'pyproject.toml') api_config = {'GLOBAL': {'opty':'10', 'optz':'10'}} with tempfile.TemporaryDirectory() as td: shutil.copy(config_filename, os.path.join(td, 'pyproject.toml')) old_cwd = os.getcwd() try: os.chdir(td) main = doit_cmd.DoitMain(extra_config=api_config) finally: os.chdir(old_cwd) assert 1 == len(main.config['COMMAND']), main.config # test loaded plugin command is actually used with plugin name assert 'bar' in main.get_cmds() # INI has higher preference the api_config assert main.config['GLOBAL'] == {'optx':'2', 'opty':'3', 'optz':'10'} doit-0.36.0/tests/test_exceptions.py000066400000000000000000000046311423054503100174740ustar00rootroot00000000000000from doit import exceptions class TestInvalidCommand(object): def test_just_string(self): exception = exceptions.InvalidCommand('whatever string') assert 'whatever string' == str(exception) def test_task_not_found(self): exception = exceptions.InvalidCommand(not_found='my_task') exception.cmd_used = 'build' assert 'command `build` invalid parameter: "my_task".' in str(exception) def test_param_not_found(self): exception = exceptions.InvalidCommand(not_found='my_task') exception.cmd_used = None want = 'Invalid parameter: "my_task". Must be a command,' assert want in str(exception) assert 'Type "doit help" to see' in str(exception) def test_custom_binary_name(self): exception = exceptions.InvalidCommand(not_found='my_task') exception.cmd_used = None exception.bin_name = 'my_tool' assert 'Type "my_tool help" to see ' in str(exception) class TestBaseFail(object): def test_name(self): class XYZ(exceptions.BaseFail): pass my_excp = XYZ("hello") assert 'XYZ' == my_excp.get_name() assert 'XYZ' in str(my_excp) assert 'XYZ' in repr(my_excp) def test_msg_notraceback(self): my_excp = exceptions.BaseFail('got you') msg = my_excp.get_msg() assert 'got you' in msg def test_exception(self): try: raise IndexError('too big') except Exception as e: my_excp = exceptions.BaseFail('got this', e) msg = my_excp.get_msg() assert 'got this' in msg assert 'too big' in msg assert 'IndexError' in msg def test_caught(self): try: raise IndexError('too big') except Exception as e: my_excp = exceptions.BaseFail('got this', e) my_excp2 = exceptions.BaseFail('handle that', my_excp) msg = my_excp2.get_msg() assert 'handle that' in msg assert 'got this' not in msg # could be there too... assert 'too big' in msg assert 'IndexError' in msg class TestAllCaught(object): def test(self): assert issubclass(exceptions.TaskFailed, exceptions.BaseFail) assert issubclass(exceptions.TaskError, exceptions.BaseFail) assert issubclass(exceptions.SetupError, exceptions.BaseFail) assert issubclass(exceptions.DependencyError, exceptions.BaseFail) doit-0.36.0/tests/test_loader.py000066400000000000000000000517021423054503100165620ustar00rootroot00000000000000import os import inspect from operator import attrgetter import pytest from doit.exceptions import InvalidDodoFile, InvalidCommand from doit.task import InvalidTask, DelayedLoader, Task from doit.loader import flat_generator, get_module from doit.loader import load_tasks, load_doit_config, generate_tasks from doit.loader import create_after, task_params class TestFlatGenerator(object): def test_nested(self): def myg(items): for x in items: yield x flat = flat_generator(myg([1, myg([2, myg([3, myg([4, myg([5])])])])])) assert [1,2,3,4,5] == [f[0] for f in flat] class TestGetModule(object): def testAbsolutePath(self, restore_cwd): fileName = os.path.join(os.path.dirname(__file__),"loader_sample.py") dodo_module = get_module(fileName) assert hasattr(dodo_module, 'task_xxx1') def testRelativePath(self, restore_cwd): # test relative import but test should still work from any path # so change cwd. this_path = os.path.join(os.path.dirname(__file__),'..') os.chdir(os.path.abspath(this_path)) fileName = "tests/loader_sample.py" dodo_module = get_module(fileName) assert hasattr(dodo_module, 'task_xxx1') def testWrongFileName(self): fileName = os.path.join(os.path.dirname(__file__),"i_dont_exist.py") pytest.raises(InvalidDodoFile, get_module, fileName) def testInParentDir(self, restore_cwd): os.chdir(os.path.join(os.path.dirname(__file__), "data")) fileName = "loader_sample.py" pytest.raises(InvalidDodoFile, get_module, fileName) get_module(fileName, seek_parent=True) # cwd is changed to location of dodo.py assert os.getcwd() == os.path.dirname(os.path.abspath(fileName)) def testWrongFileNameInParentDir(self, restore_cwd): os.chdir(os.path.join(os.path.dirname(__file__), "data")) fileName = os.path.join("i_dont_exist.py") pytest.raises(InvalidDodoFile, get_module, fileName, seek_parent=True) def testInvalidCwd(self, restore_cwd): fileName = os.path.join(os.path.dirname(__file__),"loader_sample.py") cwd = os.path.join(os.path.dirname(__file__), "dataX") pytest.raises(InvalidCommand, get_module, fileName, cwd) class TestLoadTasks(object): @pytest.fixture def dodo(self): def task_xxx1(): """task doc""" return {'actions':['do nothing']} def task_yyy2(): return {'actions':None} def task_meta(): return { 'actions' : ['do nothing'], 'meta' : { 'a' : ['b', 'c']}, } def bad_seed(): pass task_nono = 5 task_nono # pyflakes return locals() def testNormalCase(self, dodo): task_list = load_tasks(dodo) assert 3 == len(task_list) assert 'xxx1' == task_list[0].name assert 'yyy2' == task_list[1].name assert 'meta' == task_list[2].name def testCreateAfterDecorator(self): @create_after('yyy2') def task_zzz3(): # pragma: no cover pass # create_after annotates the function assert isinstance(task_zzz3.doit_create_after, DelayedLoader) assert task_zzz3.doit_create_after.task_dep == 'yyy2' def testInitialLoadDelayedTask(self, dodo): @create_after('yyy2') def task_zzz3(): # pragma: no cover raise Exception('Cant be executed on load phase') dodo['task_zzz3'] = task_zzz3 # placeholder task is created with `loader` attribute task_list = load_tasks(dodo, allow_delayed=True) z_task = [t for t in task_list if t.name=='zzz3'][0] assert z_task.loader.task_dep == 'yyy2' assert z_task.loader.creator == task_zzz3 def testInitialLoadDelayedTask_no_delayed(self, dodo): @create_after('yyy2') def task_zzz3(): yield {'basename': 'foo', 'actions': None} yield {'basename': 'bar', 'actions': None} dodo['task_zzz3'] = task_zzz3 # load tasks as done by the `list` command task_list = load_tasks(dodo, allow_delayed=False) tasks = {t.name:t for t in task_list} assert 'zzz3' not in tasks assert tasks['foo'].loader is None assert tasks['bar'].loader is None def testInitialLoadDelayedTask_creates(self, dodo): @create_after('yyy2', creates=['foo', 'bar']) def task_zzz3(): # pragma: no cover '''not loaded task doc''' raise Exception('Cant be executed on load phase') dodo['task_zzz3'] = task_zzz3 # placeholder task is created with `loader` attribute task_list = load_tasks(dodo, allow_delayed=True) tasks = {t.name:t for t in task_list} assert 'zzz3' not in tasks f_task = tasks['foo'] assert f_task.loader.task_dep == 'yyy2' assert f_task.loader.creator == task_zzz3 assert tasks['bar'].loader.task_dep == tasks['foo'].loader.task_dep assert tasks['foo'].doc == 'not loaded task doc' # make sure doit can be executed more then once in single process GH#381 list2 = load_tasks(dodo, allow_delayed=True) tasks2 = {t.name:t for t in list2} assert tasks['bar'].loader is not tasks2['bar'].loader def testCreateAfterDecoratorOnMethod(self): 'Check that class-defined tasks are loaded as bound methods' class Tasks: @create_after('yyy2') def task_zzz3(): # pragma: no cover pass # create_after annotates the function task_list = load_tasks({'task_zzz3': Tasks().task_zzz3}, allow_delayed=True) tasks = {t.name:t for t in task_list} task_zzz3 = tasks['zzz3'] assert isinstance(task_zzz3.loader, DelayedLoader) # check creator is a bound method, not a plain function assert getattr(task_zzz3.loader.creator, '__self__', None) is not None def testCreateAfterDecoratorOnMethodWithParams(self, dodo): 'Check that class-defined tasks support the creates argument of @create_after' class Tasks: @create_after('yyy2', creates=['foo', 'bar']) def task_zzz3(): # pragma: no cover '''not loaded task doc''' raise Exception('Cant be executed on load phase') # placeholder task is created with `loader` attribute task_list = load_tasks({'task_zzz3': Tasks().task_zzz3}, allow_delayed=True) tasks = {t.name:t for t in task_list} assert 'zzz3' not in tasks f_task = tasks['foo'] assert f_task.loader.task_dep == 'yyy2' assert getattr(f_task.loader.creator, '__self__', None) is not None # loaders are not the same because of #381 (multiple execution on same process) # But this is not a problem because once the task already exists, the loader is just not used. # assert tasks['bar'].loader is tasks['foo'].loader assert tasks['foo'].doc == 'not loaded task doc' def testNameInBlacklist(self): dodo_module = {'task_cmd_name': lambda:None} pytest.raises(InvalidDodoFile, load_tasks, dodo_module, ['cmd_name']) def testDocString(self, dodo): task_list = load_tasks(dodo) assert "task doc" == task_list[0].doc def testMetaInfo(self, dodo): task_list = load_tasks(dodo) assert task_list[2].meta == {'a': ['b', 'c']} def testUse_create_doit_tasks(self): def original(): pass def creator(): return {'actions': ['do nothing'], 'file_dep': ['foox']} original.create_doit_tasks = creator task_list = load_tasks({'x': original}) assert 1 == len(task_list) assert set(['foox']) == task_list[0].file_dep def testUse_create_doit_tasks_class_method(self): class Foo(object): def __init__(self): self.create_doit_tasks = self._create_doit_tasks def _create_doit_tasks(self): return {'actions': ['do nothing'], 'file_dep': ['fooy']} task_list = load_tasks({'Foo':Foo, 'foo':Foo()}) assert len(task_list) == 1 assert task_list[0].file_dep == set(['fooy']) def testUse_create_doit_tasks_basename_kwargs(self): class Foo(object): def __init__(self): @task_params([{"name": "t", "default": None, "type": list}]) def creator(**kwargs): return self._create_doit_tasks(**kwargs) creator.basename = 'my-foo' self.create_doit_tasks = creator def _create_doit_tasks(self, **kwargs): return {'actions': ['do nothing'], 'file_dep': ['fooy'], 'targets': kwargs['t']} task_list = load_tasks({'Foo':Foo, 'foo':Foo()}, task_opts={'my-foo': {'t': ['bar']}}) assert len(task_list) == 1 assert task_list[0].name == 'my-foo' assert task_list[0].targets == ['bar'] def testUse_object_methods(self): class Dodo(object): def foo(self): # pragma: no cover pass def task_method1(self): return {'actions':None} def task_method2(self): return {'actions':None} methods = dict(inspect.getmembers(Dodo())) task_list = load_tasks(methods) assert 2 == len(task_list) assert 'method1' == task_list[0].name assert 'method2' == task_list[1].name class TestTaskGeneratorParams(object): def test_task_params_annotations(self): params = [{"name": "foo", "default": "bar", "long": "foo"}] func = task_params(params)(lambda: 1) assert func._task_creator_params == params def test_default(self): 'Ensure that a task parameter can be passed to the task generator.' @task_params([{"name": "foo", "default": "bar", "long": "foo"}]) def task_foo(foo): return { 'actions': [], 'doc': foo } task_list = load_tasks({'task_foo': task_foo}) task = task_list.pop() assert task.doc == 'bar' task.init_options() assert task.options['foo'] == 'bar' def test_args(self): 'Ensure that a task generator parameter can be set from the command line.' @task_params([{"name": "fp", "default": "default p", "long": "fp"}]) def task_foo(fp): return { 'actions': [], 'doc': fp } args = ['foo', '--fp=from_arg'] task_list = load_tasks({'task_foo': task_foo}, args=args) task = task_list.pop() assert task.doc == 'from_arg' def test_call_api(self): 'Ensure that a task generator parameter can be set from direct API call' @task_params([{"name": "fp", "default": "default p", "long": "fp"}]) def task_foo(fp): return { 'actions': [], 'doc': fp } args = ['foo'] task_opts = {'foo': {'fp': 'from_api'}} task_list = load_tasks({'task_foo': task_foo}, args=args, task_opts=task_opts) task = task_list.pop() assert task.doc == 'from_api' def test_args_second(self): def task_bar(): return {'actions': []} @task_params([{"name": "foo", "default": "placeholder", "long": "foo"}]) def task_foo(foo): return { 'actions': [], 'doc': foo } args = ['bar', 'foo', '--foo=from_arg'] task_list = load_tasks({'task_foo': task_foo, 'task_bar': task_bar}, args=args) assert len(task_list) == 2 bar, foo = sorted(task_list, key=attrgetter('name')) assert foo.name == 'foo' assert foo.doc == 'from_arg' def test_config(self): @task_params([{"name": "fp", "default": "default p", "long": "fp"}]) def task_foo(fp): return { 'actions': [], 'doc': fp } config = {'task:foo': {'fp': 'from_config'}} task_list = load_tasks({'task_foo': task_foo}, args=(), config=config) task = task_list.pop() assert task.doc == 'from_config' # config is overwritten from args args = ['foo', '--fp=from_arg'] task_list2 = load_tasks({'task_foo': task_foo}, args=args, config=config) task2 = task_list2.pop() assert task2.doc == 'from_arg' def test_method(self): 'Ensure that a task parameter can be passed to the task generator defined as a class method.' class Tasks(object): @task_params([{"name": "param1", "default": "placeholder", "long": "param1"}]) def task_foo(self, param1): for i in range(2): yield { 'name': 'subtask' + str(i), 'actions': [], 'doc': param1, } foo = Tasks().task_foo task_list = load_tasks({'task_foo': foo}, args=('foo', '--param1=my_val')) assert len(task_list) == 3 tasks = {t.name: t for t in task_list} assert len(tasks['foo'].params) == 0 assert len(tasks['foo'].creator_params) == 1 assert tasks['foo'].doc == '' assert len(tasks['foo:subtask0'].params) == 0 assert tasks['foo:subtask0'].doc == 'my_val' def test_delayed(self): @create_after() @task_params([{"name": "fp", "default": "default p", "long": "fp"}]) def task_foo(fp): return { 'actions': [], 'doc': fp } args = ['foo', '--fp=from_arg'] task_list = load_tasks({'task_foo': task_foo}, allow_delayed=True, args=args) task = task_list.pop() assert task.name == 'foo' assert task.loader.kwargs == {'fp': 'from_arg'} assert len(task.creator_params) == 1 def test_dup_param(self): 'Ensure that `params` field and @task_params definitions are prohibited' @task_params([{"name": "foo", "default": "decorator", "long": "foo"}]) def task_dup(foo): return { 'actions': [], 'params': [{"name": "bar", "default": "dict", "long": "bar"}], } with pytest.raises(InvalidTask) as exc_info: load_tasks({'task_dup': task_dup}) assert ('attribute can not be used in conjuction with' in str(exc_info.value)) class TestDodoConfig(object): def testConfigType_Error(self): pytest.raises(InvalidDodoFile, load_doit_config, {'DOIT_CONFIG': 'abc'}) def testConfigDict_Ok(self,): config = load_doit_config({'DOIT_CONFIG': {'verbose': 2}}) assert {'verbose': 2} == config def testDefaultConfig_Dict(self): config = load_doit_config({'whatever': 2}) assert {} == config class TestGenerateTaskInvalid(object): def testInvalidValue(self): pytest.raises(InvalidTask, generate_tasks, "dict",'xpto 14') class TestGenerateTaskNone(object): def testEmpty(self): tasks = generate_tasks('xx', None) assert len(tasks) == 0 class TestGenerateTasksSingle(object): def testDict(self): tasks = generate_tasks("my_name", {'actions':['xpto 14']}) assert isinstance(tasks[0], Task) assert "my_name" == tasks[0].name def testTaskObj(self): tasks = generate_tasks("foo", Task('bar', None)) assert 1 == len(tasks) assert tasks[0].name == 'bar' def testBaseName(self): tasks = generate_tasks("function_name", { 'basename': 'real_task_name', 'actions':['xpto 14'] }) assert isinstance(tasks[0], Task) assert "real_task_name" == tasks[0].name # name field is only for subtasks. def testInvalidNameField(self): pytest.raises(InvalidTask, generate_tasks, "my_name", {'actions':['xpto 14'],'name':'bla bla'}) def testUseDocstring(self): tasks = generate_tasks("dict",{'actions':['xpto 14']}, "my doc") assert "my doc" == tasks[0].doc def testDocstringNotUsed(self): mytask = {'actions':['xpto 14'], 'doc':'from dict'} tasks = generate_tasks("dict", mytask, "from docstring") assert "from dict" == tasks[0].doc class TestGenerateTasksGenerator(object): def testGenerator(self): def f_xpto(): for i in range(3): yield {'name':str(i), 'actions' :["xpto -%d"%i]} tasks = generate_tasks("xpto", f_xpto()) assert isinstance(tasks[0], Task) assert 4 == len(tasks) assert tasks[0].subtask_of is None assert "xpto:0" == tasks[0].task_dep[0] assert "xpto:0" == tasks[1].name assert tasks[1].subtask_of == 'xpto' def testMultiLevelGenerator(self): def f_xpto(base_name): """second level docstring""" for i in range(3): name = "%s-%d" % (base_name, i) yield {'name':name, 'actions' :["xpto -%d"%i]} def f_first_level(): for i in range(2): yield f_xpto(str(i)) tasks = generate_tasks("xpto", f_first_level()) assert isinstance(tasks[0], Task) assert 7 == len(tasks) assert tasks[0].subtask_of is None assert f_xpto.__doc__ == tasks[0].doc assert tasks[1].subtask_of == 'xpto' assert "xpto:0-0" == tasks[1].name assert "xpto:1-2" == tasks[-1].name def testGeneratorReturnTaskObj(self): def foo(base_name): for i in range(3): name = "%s-%d" % (base_name, i) yield Task(name, actions=["xpto -%d"%i]) tasks = generate_tasks("foo", foo('bar')) assert 3 == len(tasks) assert tasks[0].name == 'bar-0' assert tasks[1].name == 'bar-1' assert tasks[2].name == 'bar-2' def testGeneratorDoesntReturnDict(self): def f_xpto(): for i in range(3): yield "xpto -%d" % i pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorDictMissingAction(self): def f_xpto(): for i in range(3): yield {'name':str(i)} pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorDictMissingName(self): def f_xpto(): for i in range(3): yield {'actions' :["xpto -%d"%i]} pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorBasename(self): def f_xpto(): for i in range(3): yield {'basename':str(i), 'actions' :["xpto"]} tasks = sorted(generate_tasks("xpto", f_xpto()), key=lambda t:t.name) assert isinstance(tasks[0], Task) assert 3 == len(tasks) assert "0" == tasks[0].name assert tasks[0].subtask_of is None assert tasks[1].subtask_of is None def testGeneratorBasenameName(self): def f_xpto(): for i in range(3): yield {'basename':'xpto', 'name':str(i), 'actions' :["a"]} tasks = sorted(generate_tasks("f_xpto", f_xpto())) assert isinstance(tasks[0], Task) assert 4 == len(tasks) assert "xpto" == tasks[0].name assert "xpto:0" == tasks[1].name assert tasks[0].subtask_of is None assert tasks[1].subtask_of == 'xpto' def testGeneratorBasenameCanNotRepeat(self): def f_xpto(): for i in range(3): yield {'basename':'again', 'actions' :["xpto"]} pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorBasenameCanNotRepeatNonGroup(self): def f_xpto(): yield {'basename': 'xpto', 'actions':["a"]} for i in range(3): yield {'name': str(i), 'actions' :["a"]} pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorNameCanNotRepeat(self): def f_xpto(): yield {'basename':'bn', 'name': 'xxx', 'actions' :["xpto"]} yield {'basename':'bn', 'name': 'xxx', 'actions' :["xpto2"]} pytest.raises(InvalidTask, generate_tasks, "xpto", f_xpto()) def testGeneratorDocString(self): def f_xpto(): "the doc" for i in range(3): yield {'name':str(i), 'actions' :["xpto -%d"%i]} tasks = sorted(generate_tasks("xpto", f_xpto(), f_xpto.__doc__)) assert "the doc" == tasks[0].doc def testGeneratorWithNoTasks(self): def f_xpto(): for x in []: yield x tasks = generate_tasks("xpto", f_xpto()) assert 1 == len(tasks) assert "xpto" == tasks[0].name assert tasks[0].subtask_of is None def testGeneratorBaseOnly(self): def f_xpto(): yield {'basename':'xpto', 'name':None, 'doc': 'xxx doc'} tasks = sorted(generate_tasks("f_xpto", f_xpto())) assert 1 == len(tasks) assert isinstance(tasks[0], Task) assert "xpto" == tasks[0].name assert tasks[0].has_subtask assert 'xxx doc' == tasks[0].doc doit-0.36.0/tests/test_plugin.py000066400000000000000000000055111423054503100166070ustar00rootroot00000000000000import sys from unittest.mock import Mock import pytest from doit.plugin import PluginEntry, PluginDict if sys.version_info < (3, 10): from importlib_metadata import EntryPoint else: from importlib.metadata import EntryPoint class TestPluginEntry(object): def test_repr(self): plugin = PluginEntry('category1', 'name1', 'mock:Mock') assert "PluginEntry('category1', 'name1', 'mock:Mock')" == repr(plugin) def test_get(self): plugin = PluginEntry('category1', 'name1', 'unittest.mock:Mock') got = plugin.get() assert got is Mock def test_load_error_module_not_found(self): plugin = PluginEntry('category1', 'name1', 'i_dont:exist') with pytest.raises(Exception) as exc_info: plugin.load() assert 'Plugin category1 module `i_dont`' in str(exc_info.value) def test_load_error_obj_not_found(self): plugin = PluginEntry('category1', 'name1', 'unittest.mock:i_dont_exist') with pytest.raises(Exception) as exc_info: plugin.load() assert ('Plugin category1:name1 module `unittest.mock`' in str(exc_info.value)) assert 'i_dont_exist' in str(exc_info.value) class TestPluginDict(object): @pytest.fixture def plugins(self): plugins = PluginDict() config_dict = {'name1': 'pytest:raises', 'name2': 'unittest.mock:Mock'} plugins.add_plugins({'category1': config_dict}, 'category1') return plugins def test_add_plugins_from_dict(self, plugins): assert len(plugins) == 2 name1 = plugins['name1'] assert isinstance(name1, PluginEntry) assert name1.category == 'category1' assert name1.name == 'name1' assert name1.location == 'pytest:raises' def test_add_plugins_from_pkg_resources(self, monkeypatch): # mock entry points from doit import plugin def fake_entries(group): yield EntryPoint(name='name1', value='pytest:raises', group=group) monkeypatch.setattr(plugin, 'entry_points_impl', lambda: fake_entries) plugins = PluginDict() plugins.add_plugins({}, 'category2') name1 = plugins['name1'] assert isinstance(name1, PluginEntry) assert name1.category == 'category2' assert name1.name == 'name1' assert name1.location == 'pytest:raises' def test_get_plugin_actual_plugin(self, plugins): assert plugins.get_plugin('name2') is Mock def test_get_plugin_not_a_plugin(self, plugins): my_val = 4 plugins['builtin-item'] = my_val assert plugins.get_plugin('builtin-item') is my_val def test_to_dict(self, plugins): expected = {'name1': pytest.raises, 'name2': Mock} assert plugins.to_dict() == expected doit-0.36.0/tests/test_reporter.py000066400000000000000000000303371423054503100171570ustar00rootroot00000000000000import sys import json from io import StringIO from doit import reporter from doit.task import Stream, Task from doit.exceptions import BaseFail, TaskFailed class TestConsoleReporter(object): def test_initialize(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.initialize([Task("t_name", None)], ["t_name"]) # no output on initialize assert "" in rep.outstream.getvalue() def test_startTask(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.get_status(Task("t_name", None)) # no output on start task assert "" in rep.outstream.getvalue() def test_executeTask(self): rep = reporter.ConsoleReporter(StringIO(), {}) def do_nothing():pass t1 = Task("with_action",[(do_nothing,)]) rep.execute_task(t1) assert ". with_action\n" == rep.outstream.getvalue() def test_executeTask_unicode(self): rep = reporter.ConsoleReporter(StringIO(), {}) def do_nothing():pass task_name = "中文 with_action" t1 = Task(task_name, [(do_nothing,)]) rep.execute_task(t1) assert ". 中文 with_action\n" == rep.outstream.getvalue() def test_executeHidden(self): rep = reporter.ConsoleReporter(StringIO(), {}) def do_nothing():pass t1 = Task("_hidden",[(do_nothing,)]) rep.execute_task(t1) assert "" == rep.outstream.getvalue() def test_executeGroupTask(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.execute_task(Task("t_name", None)) assert "" == rep.outstream.getvalue() def test_skipUptodate(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.skip_uptodate(Task("t_name", None)) assert "-- " in rep.outstream.getvalue() assert "t_name" in rep.outstream.getvalue() def test_skipUptodate_hidden(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.skip_uptodate(Task("_name", None)) assert "" == rep.outstream.getvalue() def test_skipIgnore(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.skip_ignore(Task("t_name", None)) assert "!! " in rep.outstream.getvalue() assert "t_name" in rep.outstream.getvalue() def test_cleanupError(self, capsys): rep = reporter.ConsoleReporter(StringIO(), {}) fail = TaskFailed("I got you") rep.cleanup_error(fail) err = capsys.readouterr()[1] assert "I got you" in err def test_teardownTask(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.teardown_task(Task("t_name", None)) # no output on teardown task assert "" in rep.outstream.getvalue() def test_addSuccess(self): rep = reporter.ConsoleReporter(StringIO(), {}) rep.add_success(Task("t_name", None)) # no output on success task assert "" in rep.outstream.getvalue() def test_addFailure(self): rep = reporter.ConsoleReporter(StringIO(), {}) try: raise Exception("original 中文 exception message here") except Exception as e: caught = TaskFailed("caught exception there", e) rep.add_failure(Task("t_name", None, verbosity=1), caught) rep.complete_run() got = rep.outstream.getvalue() # description assert "Exception: original 中文 exception message here" in got, got # traceback assert """raise Exception("original 中文 exception message here")""" in got # caught message assert "caught exception there" in got def test_failure_no_report(self): rep = reporter.ConsoleReporter(StringIO(), {}) failure = TaskFailed("caught exception there", Exception("ExceptionKind"), report=False) rep.add_failure(Task("t_name", None, verbosity=1), failure) rep.complete_run() got = rep.outstream.getvalue() assert "ExceptionKind" not in got assert "caught exception there" not in got def test_runtime_error(self): msg = "runtime error" rep = reporter.ConsoleReporter(StringIO(), {}) assert [] == rep.runtime_errors # no imediate output rep.runtime_error(msg) assert 1 == len(rep.runtime_errors) assert msg == rep.runtime_errors[0] assert "" in rep.outstream.getvalue() # runtime errors abort execution rep.complete_run() got = rep.outstream.getvalue() assert msg in got assert "Execution aborted" in got def test_complete_run_verbosity0(self): rep = reporter.ConsoleReporter(StringIO(), {}) caught = TaskFailed("caught exception there", Exception("foo")) task = Task("t_name", None, verbosity=0) task.executed = True rep.add_failure(task, caught) # assign new StringIO so output is only from complete_run() rep.outstream = StringIO() rep.complete_run() got = rep.outstream.getvalue() assert "" in got assert "" in got def test_complete_run_verbosity0_not_executed(self): rep = reporter.ConsoleReporter(StringIO(), {}) caught = TaskFailed("caught exception there", Exception("foo")) task = Task("t_name", None, verbosity=0) task.executed = False rep.add_failure(task, caught) # assign new StringIO so output is only from complete_run() rep.outstream = StringIO() rep.complete_run() got = rep.outstream.getvalue() assert "" not in got assert "" not in got def test_complete_run_verbosity1(self): rep = reporter.ConsoleReporter(StringIO(), {}) caught = TaskFailed("caught exception there", Exception("foo")) task = Task("t_name", None, verbosity=1) task.executed = True rep.add_failure(task, caught) # assign new StringIO so output is only from complete_run() rep.outstream = StringIO() rep.complete_run() got = rep.outstream.getvalue() assert "" in got assert "" not in got def test_complete_run_verbosity2(self): rep = reporter.ConsoleReporter(StringIO(), {}) caught = TaskFailed("caught exception there", Exception("foo")) rep.add_failure(Task("t_name", None, verbosity=2), caught) # assign new StringIO so output is only from complete_run() rep.outstream = StringIO() rep.complete_run() got = rep.outstream.getvalue() assert "" not in got assert "" not in got def test_complete_run_verbosity2_redisplay(self): rep = reporter.ConsoleReporter(StringIO(), {'failure_verbosity': 2}) caught = TaskFailed("caught exception there", Exception("foo")) task = Task("t_name", None, verbosity=2) task.executed = True rep.add_failure(task, caught) # assign new StringIO so output is only from complete_run() rep.outstream = StringIO() rep.complete_run() got = rep.outstream.getvalue() assert "" in got assert "" in got class TestExecutedOnlyReporter(object): def test_skipUptodate(self): rep = reporter.ExecutedOnlyReporter(StringIO(), {}) rep.skip_uptodate(Task("t_name", None)) assert "" == rep.outstream.getvalue() def test_skipIgnore(self): rep = reporter.ExecutedOnlyReporter(StringIO(), {}) rep.skip_ignore(Task("t_name", None)) assert "" == rep.outstream.getvalue() class TestZeroReporter(object): def test_executeTask(self): rep = reporter.ZeroReporter(StringIO(), {}) def do_nothing():pass t1 = Task("with_action",[(do_nothing,)]) rep.execute_task(t1) assert "" == rep.outstream.getvalue() def test_runtime_error(self, capsys): msg = "zero runtime error" rep = reporter.ZeroReporter(StringIO(), {}) # imediate output rep.runtime_error(msg) assert msg in capsys.readouterr()[1] class TestErrorOnlyReporter(object): def test_executeTask(self): rep = reporter.ErrorOnlyReporter(StringIO(), {}) def do_nothing():pass t1 = Task("with_action", [(do_nothing,)]) rep.execute_task(t1) assert "" == rep.outstream.getvalue() def test_faile_no_report(self): rep = reporter.ErrorOnlyReporter(StringIO(), {}) failure = TaskFailed("An error here", report=False) rep.add_failure(Task("t_name", None, verbosity=1), failure) assert "" == rep.outstream.getvalue() def test_error_report(self): class UnknownException(BaseFail): pass rep = reporter.ErrorOnlyReporter(StringIO(), {}) exception = Exception("Error message here") failure = UnknownException("Something unexpected", exception) rep.add_failure(Task("t_name", None, verbosity=1), failure) assert "Error message here" in rep.outstream.getvalue() assert "Something unexpected" in rep.outstream.getvalue() class TestTaskResult(object): def test(self): def sample(): print("this is printed") t1 = Task("t1", [(sample,)]) result = reporter.TaskResult(t1) result.start() t1.execute(Stream(0)) result.set_result('success') got = result.to_dict() assert t1.name == got['name'], got assert 'success' == got['result'], got assert "this is printed\n" == got['out'], got assert "" == got['err'], got assert got['started'] assert 'elapsed' in got class TestJsonReporter(object): def test_normal(self): output = StringIO() rep = reporter.JsonReporter(output) t1 = Task("t1", None) t2 = Task("t2", None) t3 = Task("t3", None) t4 = Task("t4", None) expected = {'t1':'fail', 't2':'up-to-date', 't3':'success', 't4':'ignore'} # t1 fail rep.get_status(t1) rep.execute_task(t1) rep.add_failure(t1, TaskFailed('t1 failed!')) # t2 skipped rep.get_status(t2) rep.skip_uptodate(t2) # t3 success rep.get_status(t3) rep.execute_task(t3) rep.add_success(t3) # t4 ignore rep.get_status(t4) rep.skip_ignore(t4) rep.teardown_task(t4) rep.complete_run() got = json.loads(output.getvalue()) for task_result in got['tasks']: assert expected[task_result['name']] == task_result['result'], got if task_result['name'] == 't1': assert 't1 failed!' in task_result['error'] def test_cleanup_error(self, capsys): output = StringIO() rep = reporter.JsonReporter(output) t1 = Task("t1", None) msg = "cleanup error" fail = TaskFailed(msg) assert [] == rep.errors rep.get_status(t1) rep.execute_task(t1) rep.add_success(t1) rep.cleanup_error(fail) assert [msg+'\n'] == rep.errors assert "" in rep.outstream.getvalue() rep.complete_run() got = json.loads(output.getvalue()) assert msg in got['err'] def test_runtime_error(self): output = StringIO() rep = reporter.JsonReporter(output) t1 = Task("t1", None) msg = "runtime error" assert [] == rep.errors rep.get_status(t1) rep.execute_task(t1) rep.add_success(t1) rep.runtime_error(msg) assert [msg] == rep.errors assert "" in rep.outstream.getvalue() # runtime errors abort execution rep.complete_run() got = json.loads(output.getvalue()) assert msg in got['err'] def test_ignore_stdout(self): output = StringIO() rep = reporter.JsonReporter(output) sys.stdout.write("info that doesnt belong to any task...") sys.stderr.write('something on err') t1 = Task("t1", None) expected = {'t1':'success'} rep.get_status(t1) rep.execute_task(t1) rep.add_success(t1) rep.complete_run() got = json.loads(output.getvalue()) assert expected[got['tasks'][0]['name']] == got['tasks'][0]['result'] assert "info that doesnt belong to any task..." == got['out'] assert "something on err" == got['err'] doit-0.36.0/tests/test_runner.py000066400000000000000000001004301423054503100166160ustar00rootroot00000000000000import os import sys import pickle from multiprocessing import Queue import platform from unittest.mock import Mock import pytest from doit.exceptions import BaseFail, InvalidTask from doit.dependency import DbmDB, Dependency from doit.reporter import ConsoleReporter from doit.task import Task, DelayedLoader from doit.control import TaskDispatcher, ExecNode from doit import runner PLAT_IMPL = platform.python_implementation() # sample actions def my_print(*args): pass def _fail(): return False def _error(): raise Exception("I am the exception.\n") def _exit(): raise SystemExit() def simple_result(): print("success output") print("success error", file=sys.stderr) return 'my-result' def simple_fail(): print("simple output") print("simple error", file=sys.stderr) raise Exception('this task failed') class FakeReporter(object): """Just log everything in internal attribute - used on tests""" def __init__(self, with_exceptions=False, outstream=None, options=None): # include Exception object of log failures self.with_exceptions = with_exceptions self.log = [] def get_status(self, task): self.log.append(('start', task)) def execute_task(self, task): self.log.append(('execute', task)) def add_failure(self, task, exception): if self.with_exceptions: self.log.append(('fail', task, exception)) else: self.log.append(('fail', task)) def add_success(self, task): self.log.append(('success', task)) def skip_uptodate(self, task): self.log.append(('up-to-date', task)) def skip_ignore(self, task): self.log.append(('ignore', task)) def cleanup_error(self, exception): self.log.append(('cleanup_error',)) def runtime_error(self, msg): self.log.append(('runtime_error',)) def teardown_task(self, task): self.log.append(('teardown', task)) def complete_run(self): pass @pytest.fixture def reporter(request): return FakeReporter() class TestRunner(object): def testInit(self, reporter, dep_manager): my_runner = runner.Runner(dep_manager, reporter) assert False == my_runner._stop_running assert runner.SUCCESS == my_runner.final_result class TestRunner_SelectTask(object): def test_ready(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )]) my_runner = runner.Runner(dep_manager, reporter) assert True == my_runner.select_task(ExecNode(t1, None), {}) assert ('start', t1) == reporter.log.pop(0) assert not reporter.log def test_DependencyError(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )], file_dep=["i_dont_exist"]) my_runner = runner.Runner(dep_manager, reporter) assert False == my_runner.select_task(ExecNode(t1, None), {}) assert ('start', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) assert not reporter.log def test_upToDate(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )], file_dep=[__file__]) my_runner = runner.Runner(dep_manager, reporter) my_runner.dep_manager.save_success(t1) assert False == my_runner.select_task(ExecNode(t1, None), {}) assert ('start', t1) == reporter.log.pop(0) assert ('up-to-date', t1) == reporter.log.pop(0) assert not reporter.log def test_ignore(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )]) my_runner = runner.Runner(dep_manager, reporter) my_runner.dep_manager.ignore(t1) assert False == my_runner.select_task(ExecNode(t1, None), {}) assert ('start', t1) == reporter.log.pop(0) assert ('ignore', t1) == reporter.log.pop(0) assert not reporter.log def test_alwaysExecute(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )], uptodate=[True]) my_runner = runner.Runner(dep_manager, reporter, always_execute=True) my_runner.dep_manager.save_success(t1) n1 = ExecNode(t1, None) assert True == my_runner.select_task(n1, {}) # run_status is set to run even if task is up-to-date assert n1.run_status == 'run' assert ('start', t1) == reporter.log.pop(0) assert not reporter.log def test_noSetup_ok(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )]) my_runner = runner.Runner(dep_manager, reporter) assert True == my_runner.select_task(ExecNode(t1, None), {}) assert ('start', t1) == reporter.log.pop(0) assert not reporter.log def test_withSetup(self, reporter, dep_manager): t1 = Task("taskX", [(my_print, ["out a"] )], setup=["taskY"]) my_runner = runner.Runner(dep_manager, reporter) # defer execution n1 = ExecNode(t1, None) assert False == my_runner.select_task(n1, {}) assert ('start', t1) == reporter.log.pop(0) assert not reporter.log # trying to select again assert True == my_runner.select_task(n1, {}) assert not reporter.log def test_getargs_ok(self, reporter, dep_manager): def ok(): return {'x':1} def check_x(my_x): return my_x == 1 t1 = Task('t1', [(ok,)]) n1 = ExecNode(t1, None) t2 = Task('t2', [(check_x,)], getargs={'my_x':('t1','x')}) n2 = ExecNode(t2, None) tasks_dict = {'t1': t1, 't2':t2} my_runner = runner.Runner(dep_manager, reporter) # t2 gives chance for setup tasks to be executed assert False == my_runner.select_task(n2, tasks_dict) assert ('start', t2) == reporter.log.pop(0) # execute task t1 to calculate value assert True == my_runner.select_task(n1, tasks_dict) assert ('start', t1) == reporter.log.pop(0) t1_result = my_runner.execute_task(t1) assert ('execute', t1) == reporter.log.pop(0) my_runner.process_task_result(n1, t1_result) assert ('success', t1) == reporter.log.pop(0) # t2.options are set on select_task assert True == my_runner.select_task(n2, tasks_dict) assert not reporter.log assert {'my_x': 1} == t2.options def test_getargs_fail(self, reporter, dep_manager): # invalid getargs. Exception wil be raised and task will fail def check_x(my_x): return True t1 = Task('t1', [lambda :True]) n1 = ExecNode(t1, None) t2 = Task('t2', [(check_x,)], getargs={'my_x':('t1','x')}) n2 = ExecNode(t2, None) tasks_dict = {'t1': t1, 't2':t2} my_runner = runner.Runner(dep_manager, reporter) # t2 gives chance for setup tasks to be executed assert False == my_runner.select_task(n2, tasks_dict) assert ('start', t2) == reporter.log.pop(0) # execute task t1 to calculate value assert True == my_runner.select_task(n1, tasks_dict) assert ('start', t1) == reporter.log.pop(0) t1_result = my_runner.execute_task(t1) assert ('execute', t1) == reporter.log.pop(0) my_runner.process_task_result(n1, t1_result) assert ('success', t1) == reporter.log.pop(0) # select_task t2 fails assert False == my_runner.select_task(n2, tasks_dict) assert ('fail', t2) == reporter.log.pop(0) assert not reporter.log def test_getargs_dict(self, reporter, dep_manager): def ok(): return {'x':1} t1 = Task('t1', [(ok,)]) n1 = ExecNode(t1, None) t2 = Task('t2', None, getargs={'my_x':('t1', None)}) tasks_dict = {'t1': t1, 't2':t2} my_runner = runner.Runner(dep_manager, reporter) t1_result = my_runner.execute_task(t1) my_runner.process_task_result(n1, t1_result) # t2.options are set on _get_task_args my_runner._get_task_args(t2, tasks_dict) assert {'my_x': {'x':1}} == t2.options def test_getargs_group(self, reporter, dep_manager): def ok(): return {'x':1} t1 = Task('t1', None, task_dep=['t1:a'], has_subtask=True) t1a = Task('t1:a', [(ok,)], subtask_of='t1') t2 = Task('t2', None, getargs={'my_x':('t1', None)}) tasks_dict = {'t1': t1, 't1a':t1a, 't2':t2} my_runner = runner.Runner(dep_manager, reporter) t1a_result = my_runner.execute_task(t1a) my_runner.process_task_result(ExecNode(t1a, None), t1a_result) # t2.options are set on _get_task_args my_runner._get_task_args(t2, tasks_dict) assert {'my_x': {'a':{'x':1}} } == t2.options def test_getargs_group_value(self, reporter, dep_manager): def ok(): return {'x':1} t1 = Task('t1', None, task_dep=['t1:a'], has_subtask=True) t1a = Task('t1:a', [(ok,)], subtask_of='t1') t2 = Task('t2', None, getargs={'my_x':('t1', 'x')}) tasks_dict = {'t1': t1, 't1a':t1a, 't2':t2} my_runner = runner.Runner(dep_manager, reporter) t1a_result = my_runner.execute_task(t1a) my_runner.process_task_result(ExecNode(t1a, None), t1a_result) # t2.options are set on _get_task_args my_runner._get_task_args(t2, tasks_dict) assert {'my_x': {'a':1} } == t2.options class TestTask_Teardown(object): def test_ok(self, reporter, dep_manager): touched = [] def touch(): touched.append(1) t1 = Task('t1', [], teardown=[(touch,)]) my_runner = runner.Runner(dep_manager, reporter) my_runner.teardown_list = [t1] t1.execute(my_runner.stream) my_runner.teardown() assert 1 == len(touched) assert ('teardown', t1) == reporter.log.pop(0) assert not reporter.log def test_reverse_order(self, reporter, dep_manager): def do_nothing():pass t1 = Task('t1', [], teardown=[do_nothing]) t2 = Task('t2', [], teardown=[do_nothing]) my_runner = runner.Runner(dep_manager, reporter) my_runner.teardown_list = [t1, t2] t1.execute(my_runner.stream) t2.execute(my_runner.stream) my_runner.teardown() assert ('teardown', t2) == reporter.log.pop(0) assert ('teardown', t1) == reporter.log.pop(0) assert not reporter.log def test_errors(self, reporter, dep_manager): def raise_something(x): raise Exception(x) t1 = Task('t1', [], teardown=[(raise_something,['t1 blow'])]) t2 = Task('t2', [], teardown=[(raise_something,['t2 blow'])]) my_runner = runner.Runner(dep_manager, reporter) my_runner.teardown_list = [t1, t2] t1.execute(my_runner.stream) t2.execute(my_runner.stream) my_runner.teardown() assert ('teardown', t2) == reporter.log.pop(0) assert ('cleanup_error',) == reporter.log.pop(0) assert ('teardown', t1) == reporter.log.pop(0) assert ('cleanup_error',) == reporter.log.pop(0) assert not reporter.log class TestTask_RunAll(object): def test_reporter_runtime_error(self, reporter, dep_manager): t1 = Task('t1', [], calc_dep=['t2']) t2 = Task('t2', [lambda: {'file_dep':[1]}]) my_runner = runner.Runner(dep_manager, reporter) my_runner.run_all(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert runner.ERROR == my_runner.final_result assert ('start', t2) == reporter.log.pop(0) assert ('execute', t2) == reporter.log.pop(0) assert ('success', t2) == reporter.log.pop(0) assert ('runtime_error',) == reporter.log.pop(0) assert not reporter.log # run tests in both single process runner and multi-process runner RUNNERS = [runner.Runner, runner.MThreadRunner] # TODO: test should be added and skipped! if runner.MRunner.available(): RUNNERS.append(runner.MRunner) @pytest.fixture(params=RUNNERS) def RunnerClass(request): return request.param # function used on actions, define here to make sure they are pickable def ok(): return "ok" def ok2(): return "different" def my_action(): import sys sys.stdout.write('out here') sys.stderr.write('err here') return {'bb': 5} def use_args(arg1): print(arg1) def make_args(): return {'myarg':1} def action_add_filedep(task, extra_dep): task.file_dep.add(extra_dep) class TestRunner_run_tasks(object): def test_teardown(self, reporter, RunnerClass, dep_manager): t1 = Task('t1', [], teardown=[ok]) t2 = Task('t2', []) my_runner = RunnerClass(dep_manager, reporter) assert [] == my_runner.teardown_list my_runner.run_tasks(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) my_runner.finish() assert ('teardown', t1) == reporter.log[-1] # testing whole process/API def test_success(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(my_print, ["out a"] )] ) t2 = Task("t2", [(my_print, ["out a"] )] ) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert runner.SUCCESS == my_runner.finish() assert ('start', t1) == reporter.log.pop(0), reporter.log assert ('execute', t1) == reporter.log.pop(0) assert ('success', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('execute', t2) == reporter.log.pop(0) assert ('success', t2) == reporter.log.pop(0) # test result, value, out, err are saved into task def test_result(self, reporter, RunnerClass, dep_manager): task = Task("taskY", [my_action] ) my_runner = RunnerClass(dep_manager, reporter) assert None == task.result assert {} == task.values assert [None] == [a.out for a in task.actions] assert [None] == [a.err for a in task.actions] my_runner.run_tasks(TaskDispatcher({'taskY':task}, [], ['taskY'])) assert runner.SUCCESS == my_runner.finish() assert {'bb': 5} == task.result assert {'bb': 5} == task.values assert ['out here'] == [a.out for a in task.actions] assert ['err here'] == [a.err for a in task.actions] # whenever a task fails remaining task are not executed def test_failureOutput(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [_fail]) t2 = Task("t2", [_fail]) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert runner.FAILURE == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) # second task is not executed assert 0 == len(reporter.log) def test_error(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [_error]) t2 = Task("t2", [_error]) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert runner.ERROR == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) # second task is not executed assert 0 == len(reporter.log) def test_dependency_error_after_execution(self, dep_manager): t1 = Task("t1", [(my_print, ["out a"] )], file_dep=["i_dont_exist"], targets=['not_there']) reporter = FakeReporter(with_exceptions=True) my_runner = runner.Runner(dep_manager, reporter) # Missing file_dep is not caught because check is short-circuited by # missing target. my_runner.run_tasks(TaskDispatcher({'t1':t1}, [], ['t1'])) assert runner.ERROR == my_runner.finish() print(reporter.log) assert ('start', t1) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) fail_log = reporter.log.pop(0) assert ('fail', t1) == fail_log[:2] assert "Dependent file 'i_dont_exist' does not exist" in str(fail_log[2]) assert not reporter.log # when successful dependencies are updated def test_updateDependencies(self, reporter, RunnerClass, depfile_name): depPath = os.path.join(os.path.dirname(__file__), "data", "dependency1") ff = open(depPath,"a") ff.write("xxx") ff.close() dependencies = [depPath] filePath = os.path.join(os.path.dirname(__file__), "data", "target") ff = open(filePath,"a") ff.write("xxx") ff.close() targets = [filePath] t1 = Task("t1", [my_print], dependencies, targets) dep_manager = Dependency(DbmDB, depfile_name) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1}, [], ['t1'])) assert runner.SUCCESS == my_runner.finish() d = Dependency(DbmDB, depfile_name) assert d._get("t1", os.path.abspath(depPath)) def test_continue(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(_fail,)] ) t2 = Task("t2", [(_error,)] ) t3 = Task("t3", [(ok,)]) my_runner = RunnerClass(dep_manager, reporter, continue_=True) disp = TaskDispatcher({'t1':t1, 't2':t2, 't3':t3}, [], ['t1', 't2', 't3']) my_runner.run_tasks(disp) assert runner.ERROR == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('execute', t2) == reporter.log.pop(0) assert ('fail', t2) == reporter.log.pop(0) assert ('start', t3) == reporter.log.pop(0) assert ('execute', t3) == reporter.log.pop(0) assert ('success', t3) == reporter.log.pop(0) assert 0 == len(reporter.log) def test_continue_dont_execute_parent_of_failed_task(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(_error,)] ) t2 = Task("t2", [(ok,)], task_dep=['t1']) t3 = Task("t3", [(ok,)]) my_runner = RunnerClass(dep_manager, reporter, continue_=True) disp = TaskDispatcher({'t1':t1, 't2':t2, 't3':t3}, [], ['t1', 't2', 't3']) my_runner.run_tasks(disp) assert runner.ERROR == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('fail', t2) == reporter.log.pop(0) assert ('start', t3) == reporter.log.pop(0) assert ('execute', t3) == reporter.log.pop(0) assert ('success', t3) == reporter.log.pop(0) assert 0 == len(reporter.log) def test_continue_dep_error(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(ok,)], file_dep=['i_dont_exist'] ) t2 = Task("t2", [(ok,)], task_dep=['t1']) my_runner = RunnerClass(dep_manager, reporter, continue_=True) disp = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) my_runner.run_tasks(disp) assert runner.ERROR == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('fail', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('fail', t2) == reporter.log.pop(0) assert 0 == len(reporter.log) def test_continue_ignored_dep(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(ok,)], ) t2 = Task("t2", [(ok,)], task_dep=['t1']) my_runner = RunnerClass(dep_manager, reporter, continue_=True) my_runner.dep_manager.ignore(t1) disp = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) my_runner.run_tasks(disp) assert runner.SUCCESS == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('ignore', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('ignore', t2) == reporter.log.pop(0) assert 0 == len(reporter.log) def test_getargs(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [(use_args,)], getargs=dict(arg1=('t2','myarg')) ) t2 = Task("t2", [(make_args,)]) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert runner.SUCCESS == my_runner.finish() assert ('start', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('execute', t2) == reporter.log.pop(0) assert ('success', t2) == reporter.log.pop(0) assert ('execute', t1) == reporter.log.pop(0) assert ('success', t1) == reporter.log.pop(0) assert 0 == len(reporter.log) def testActionModifiesFiledep(self, reporter, RunnerClass, dep_manager): extra_dep = os.path.join(os.path.dirname(__file__), 'sample_md5.txt') t1 = Task("t1", [(my_print, ["out a"] ), (action_add_filedep, (), {'extra_dep': extra_dep}) ] ) my_runner = RunnerClass(dep_manager, reporter) my_runner.run_tasks(TaskDispatcher({'t1':t1}, [], ['t1'])) assert runner.SUCCESS == my_runner.finish() assert ('start', t1) == reporter.log.pop(0), reporter.log assert ('execute', t1) == reporter.log.pop(0) assert ('success', t1) == reporter.log.pop(0) assert t1.file_dep == set([extra_dep]) # SystemExit runner should not interfere with SystemExit def testSystemExitRaises(self, reporter, RunnerClass, dep_manager): t1 = Task("t1", [_exit]) my_runner = RunnerClass(dep_manager, reporter) disp = TaskDispatcher({'t1':t1}, [], ['t1']) pytest.raises(SystemExit, my_runner.run_tasks, disp) my_runner.finish() @pytest.mark.skipif('not runner.MRunner.available()') class TestMReporter(object): class MyRunner(object): def __init__(self): self.result_q = Queue() def testReporterMethod(self, reporter): fake_runner = self.MyRunner() mp_reporter = runner.MReporter(fake_runner, reporter) my_task = Task("task x", []) mp_reporter.add_success(my_task) # note limit is 2 seconds because of http://bugs.python.org/issue17707 got = fake_runner.result_q.get(True, 2) assert {'name': "task x", "reporter": 'add_success'} == got def testNonReporterMethod(self, reporter): fake_runner = self.MyRunner() mp_reporter = runner.MReporter(fake_runner, reporter) assert hasattr(mp_reporter, 'add_success') assert not hasattr(mp_reporter, 'no_existent_method') class TestJobTask(object): def test_closure_is_picklable(self): # can pickle because we use cloudpickle def non_top_function(): return 4 t1 = Task('t1', [non_top_function]) t1p = runner.JobTask(t1).task_pickle t2 = pickle.loads(t1p) assert 4 == t2.actions[0].py_callable() def test_not_picklable_raises_InvalidTask(self): def non_top_function(): pass class Unpicklable: def __getstate__(self): raise pickle.PicklingError("DO NOT PICKLE") d1 = Unpicklable() t1 = Task('t1', [non_top_function, (d1,)]) pytest.raises(InvalidTask, runner.JobTask, t1) # multiprocessing on Windows requires the whole object to be pickable def test_MRunner_pickable(dep_manager): t1 = Task('t1', []) import sys reporter = ConsoleReporter(sys.stdout, {}) run = runner.MRunner(dep_manager, reporter) run._run_tasks_init(TaskDispatcher({'t1':t1}, [], ['t1'])) # assert nothing is raised pickle.dumps(run) @pytest.mark.skipif('not runner.MRunner.available()') class TestMRunner_get_next_job(object): # simple normal case def test_run_task(self, reporter, dep_manager): t1 = Task('t1', []) t2 = Task('t2', []) run = runner.MRunner(dep_manager, reporter) run._run_tasks_init(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert t1.name == run.get_next_job(None).name assert t2.name == run.get_next_job(None).name assert None == run.get_next_job(None) def test_stop_running(self, reporter, dep_manager): t1 = Task('t1', []) t2 = Task('t2', []) run = runner.MRunner(dep_manager, reporter) run._run_tasks_init(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) assert t1.name == run.get_next_job(None).name run._stop_running = True assert None == run.get_next_job(None) def test_waiting(self, reporter, dep_manager): t1 = Task('t1', []) t2 = Task('t2', [], setup=('t1',)) run = runner.MRunner(dep_manager, reporter) dispatcher = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t2']) run._run_tasks_init(dispatcher) # first start task 1 j1 = run.get_next_job(None) assert t1.name == j1.name # hold until t1 is done assert isinstance(run.get_next_job(None), runner.JobHold) assert isinstance(run.get_next_job(None), runner.JobHold) n1 = dispatcher.nodes[j1.name] n1.run_status = 'done' j2 = run.get_next_job(n1) assert t2.name == j2.name assert None == run.get_next_job(dispatcher.nodes[j2.name]) def test_waiting_controller(self, reporter, dep_manager): t1 = Task('t1', []) t2 = Task('t2', [], calc_dep=('t1',)) run = runner.MRunner(dep_manager, reporter) run._run_tasks_init(TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2'])) # first task ok assert t1.name == run.get_next_job(None).name # hold until t1 finishes assert 0 == run.free_proc assert isinstance(run.get_next_job(None), runner.JobHold) assert 1 == run.free_proc def test_delayed_loaded(self, reporter, dep_manager): def create(): return {'basename':'t1', 'actions': None} t1 = Task('t1', [], loader=DelayedLoader(create, executed='t2')) t2 = Task('t2', []) run = runner.MRunner(dep_manager, reporter) dispatcher = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) run._run_tasks_init(dispatcher) assert t2.name == run.get_next_job(None).name assert runner.JobHold.type == run.get_next_job(None).type # after t2 is done t1 can be dispatched n2 = dispatcher.nodes[t2.name] n2.run_status = 'done' j1 = run.get_next_job(n2) assert t1.name == j1.name # the job for t1 contains the whole task since sub-process dont # have it assert j1.type == runner.JobTask.type @pytest.mark.skipif('not runner.MRunner.available()') class TestMRunner_start_process(object): # 2 process, 3 tasks def test_all_processes(self, reporter, monkeypatch, dep_manager): mock_process = Mock() monkeypatch.setattr(runner.MRunner, 'Child', mock_process) t1 = Task('t1', []) t2 = Task('t2', []) td = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) run = runner.MRunner(dep_manager, reporter, num_process=2) run._run_tasks_init(td) result_q = Queue() task_q = Queue() proc_list = run._run_start_processes(task_q, result_q) run.finish() assert 2 == len(proc_list) assert t1.name == task_q.get().name assert t2.name == task_q.get().name # 2 process, 1 task def test_less_processes(self, reporter, monkeypatch, dep_manager): mock_process = Mock() monkeypatch.setattr(runner.MRunner, 'Child', mock_process) t1 = Task('t1', []) td = TaskDispatcher({'t1':t1}, [], ['t1']) run = runner.MRunner(dep_manager, reporter, num_process=2) run._run_tasks_init(td) result_q = Queue() task_q = Queue() proc_list = run._run_start_processes(task_q, result_q) run.finish() assert 1 == len(proc_list) assert t1.name == task_q.get().name # 2 process, 2 tasks (but only one task can be started) def test_waiting_process(self, reporter, monkeypatch, dep_manager): mock_process = Mock() monkeypatch.setattr(runner.MRunner, 'Child', mock_process) t1 = Task('t1', []) t2 = Task('t2', [], task_dep=['t1']) td = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) run = runner.MRunner(dep_manager, reporter, num_process=2) run._run_tasks_init(td) result_q = Queue() task_q = Queue() proc_list = run._run_start_processes(task_q, result_q) run.finish() assert 2 == len(proc_list) assert t1.name == task_q.get().name assert isinstance(task_q.get(), runner.JobHold) def non_pickable_creator(): return {'basename': 't2', 'actions': [lambda: True]} class TestMRunner_parallel_run_tasks(object): @pytest.mark.skipif('not runner.MRunner.available()') def test_task_cloudpicklabe_multiprocess(self, reporter, dep_manager): t1 = Task("t1", [(my_print, ["out a"] )] ) t2 = Task("t2", None, loader=DelayedLoader( non_pickable_creator, executed='t1')) my_runner = runner.MRunner(dep_manager, reporter) dispatcher = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) my_runner.run_tasks(dispatcher) assert runner.SUCCESS == my_runner.finish() def test_task_not_picklabe_thread(self, reporter, dep_manager): t1 = Task("t1", [(my_print, ["out a"] )] ) t2 = Task("t2", None, loader=DelayedLoader( non_pickable_creator, executed='t1')) my_runner = runner.MThreadRunner(dep_manager, reporter) dispatcher = TaskDispatcher({'t1':t1, 't2':t2}, [], ['t1', 't2']) # threaded code have no problems with closures my_runner.run_tasks(dispatcher) assert runner.SUCCESS == my_runner.finish() assert ('start', t1) == reporter.log.pop(0), reporter.log assert ('execute', t1) == reporter.log.pop(0) assert ('success', t1) == reporter.log.pop(0) assert ('start', t2) == reporter.log.pop(0) assert ('execute', t2) == reporter.log.pop(0) assert ('success', t2) == reporter.log.pop(0) @pytest.mark.skipif('not runner.MRunner.available()') class TestMRunner_execute_task(object): def test_hold(self, reporter, dep_manager): run = runner.MRunner(dep_manager, reporter) task_q = Queue() task_q.put(runner.JobHold()) # to test task_q.put(None) # to terminate function result_q = Queue() run.execute_task_subprocess(task_q, result_q, reporter.__class__) run.finish() # nothing was done assert result_q.empty() def test_full_task(self, reporter, dep_manager): # test execute_task_subprocess can receive a full Task object run = runner.MRunner(dep_manager, reporter) t1 = Task('t1', [simple_result]) task_q = Queue() task_q.put(runner.JobTask(t1)) # to test task_q.put(None) # to terminate function result_q = Queue() run.execute_task_subprocess(task_q, result_q, reporter.__class__) run.finish() # check result assert result_q.get() == {'name': 't1', 'reporter': 'execute_task'} res = result_q.get() assert res['task']['result'] == 'my-result' assert res['task']['executed'] assert res['out'] == ['success output\n'] assert res['err'] == ['success error\n'] assert result_q.empty() def test_full_task_fail(self, reporter, dep_manager): # test execute_task_subprocess can receive a full Task object run = runner.MRunner(dep_manager, reporter) t1 = Task('t1', [simple_fail]) task_q = Queue() task_q.put(runner.JobTask(t1)) # to test task_q.put(None) # to terminate function result_q = Queue() run.execute_task_subprocess(task_q, result_q, reporter.__class__) run.finish() # check result assert result_q.get() == {'name': 't1', 'reporter': 'execute_task'} res = result_q.get() assert res['name'] == 't1' assert isinstance(res['failure'], BaseFail) assert res['out'] == ['simple output\n'] assert res['err'] == ['simple error\n'] # assert result_q.get()['task']['result'] == 'my-result' assert result_q.empty() def test_MThreadRunner_available(): assert runner.MThreadRunner.available() == True doit-0.36.0/tests/test_task.py000066400000000000000000000575401423054503100162640ustar00rootroot00000000000000import os, shutil import tempfile from io import StringIO from pathlib import Path, PurePath from sys import executable from collections.abc import Iterable import pytest from doit.exceptions import TaskError from doit.exceptions import BaseFail from doit import action from doit import task from doit.task import Stream #path to test folder TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) class TestStream(): def test_from_task(self): # use value from task, not global from Stream v0 = Stream(0) assert v0.effective_verbosity(1) == 1 assert v0.effective_verbosity(2) == 2 v2 = Stream(2) assert v2.effective_verbosity(0) == 0 assert v2.effective_verbosity(1) == 1 def test_force_global(self): # use value from task, not global from Stream v0 = Stream(0, force_global=True) assert v0.effective_verbosity(2) == 0 v2 = Stream(2, force_global=True) assert v2.effective_verbosity(0) == 2 def test_task_verbosity_not_specified(self): # default v0 = Stream(None) assert v0.effective_verbosity(None) == 1 v2 = Stream(2) assert v2.effective_verbosity(None) == 2 class TestTaskCheckInput(object): def testOkType(self): task.Task.check_attr('xxx', 'attr', [], ((int, list),())) def testOkTypeABC(self): task.Task.check_attr('xxx', 'attr', {}, ((Iterable,),())) def testOkValue(self): task.Task.check_attr('xxx', 'attr', None, ((list,), (None,))) def testFailType(self): pytest.raises(task.InvalidTask, task.Task.check_attr, 'xxx', 'attr', int, ((list,), (False,))) def testFailValue(self): pytest.raises(task.InvalidTask, task.Task.check_attr, 'xxx', 'attr', True, ((list,), (False,))) class TestTaskCompare(object): def test_equal(self): # only task name is used to compare for equality t1 = task.Task("foo", None) t2 = task.Task("bar", None) t3 = task.Task("foo", None) assert t1 != t2 assert t1 == t3 def test_lt(self): # task name is used to compare/sort tasks t1 = task.Task("foo", None) t2 = task.Task("bar", None) t3 = task.Task("gee", None) assert t1 > t2 sorted_names = sorted(t.name for t in (t1,t2,t3)) assert sorted_names == ['bar', 'foo', 'gee'] class TestTaskInit(object): def test_groupTask(self): # group tasks have no action t = task.Task("taskX", None) assert t.actions == [] def test_dependencySequenceIsValid(self): task.Task("Task X", ["taskcmd"], file_dep=["123","456"]) # dependency must be a sequence or bool. # give proper error message when anything else is used. def test_dependencyNotSequence(self): filePath = "data/dependency1" pytest.raises(task.InvalidTask, task.Task, "Task X",["taskcmd"], file_dep=filePath) def test_options(self): # when task is created, options contain the default values p1 = {'name':'p1', 'default':'p1-default'} p2 = {'name':'p2', 'default':'', 'short':'m'} t = task.Task("MyName", None, params=[p1, p2], pos_arg='pos') t.execute(Stream(0)) assert 'p1-default' == t.options['p1'] assert '' == t.options['p2'] assert 'pos' == t.pos_arg assert None == t.pos_arg_val # always uninitialized def test_options_from_cfg(self): # Ensure that doit.cfg can specify task options. p1 = {'name': 'x', 'long': 'x', 'default': None} t = task.Task("MyName", None, params=[p1]) t.cfg_values = {'x': 1} assert t.options is None t.init_options() assert t.options is not None assert 1 == t.options['x'] def test_options_from_cfg_override(self): # Ensure that doit.cfg specified task options can be replaced by # command line specified options. p1 = {'name': 'x', 'long': 'x', 'default': None, 'type': int} p2 = {'name': 'y', 'long': 'y', 'default': 2, 'type': int} t = task.Task("MyName", None, params=[p1, p2]) t.cfg_values = {'x': 1} assert t.options is None t.init_options(['--x=2']) assert t.options is not None assert 2 == t.options['x'] assert 2 == t.options['y'] def test_setup(self): t = task.Task("task5", ['action'], setup=["task2"]) assert ["task2"] == t.setup_tasks def test_forbid_equal_sign_on_name(self): pytest.raises(task.InvalidTask, task.Task, "a=1", ["taskcmd"]) class TestTaskValueSavers(object): def test_execute_value_savers(self): t = task.Task("Task X", ["taskcmd"]) t.value_savers.append(lambda: {'v1':1}) t.save_extra_values() assert 1 == t.values['v1'] class TestTaskUpToDate(object): def test_FalseRunalways(self): t = task.Task("Task X", ["taskcmd"], uptodate=[False]) assert t.uptodate == [(False, None, None)] def test_NoneIgnored(self): t = task.Task("Task X", ["taskcmd"], uptodate=[None]) assert t.uptodate == [(None, None, None)] def test_callable_function(self): def custom_check(): return True t = task.Task("Task X", ["taskcmd"], uptodate=[custom_check]) assert t.uptodate[0] == (custom_check, [], {}) def test_callable_instance_method(self): class Base(object): def check(self): return True base = Base() t = task.Task("Task X", ["taskcmd"], uptodate=[base.check]) assert t.uptodate[0] == (base.check, [], {}) def test_tuple(self): def custom_check(pos_arg, xxx=None): return True t = task.Task("Task X", ["taskcmd"], uptodate=[(custom_check, [123], {'xxx':'yyy'})]) assert t.uptodate[0] == (custom_check, [123], {'xxx':'yyy'}) def test_str(self): t = task.Task("Task X", ["taskcmd"], uptodate=['my-cmd xxx']) assert t.uptodate[0] == ('my-cmd xxx', [], {}) def test_object_with_configure(self): class Check(object): def __call__(self): return True def configure_task(self, task): task.task_dep.append('y1') check = Check() t = task.Task("Task X", ["taskcmd"], uptodate=[check]) assert (check, [], {}) == t.uptodate[0] assert ['y1'] == t.task_dep def test_invalid(self): pytest.raises(task.InvalidTask, task.Task, "Task X", ["taskcmd"], uptodate=[{'x':'y'}]) class TestTaskExpandFileDep(object): def test_dependencyStringIsFile(self): my_task = task.Task("Task X", ["taskcmd"], file_dep=["123","456"]) assert set(["123","456"]) == my_task.file_dep def test_file_dep_path(self): my_task = task.Task("Task X", ["taskcmd"], file_dep=["123", Path("456"), PurePath("789")]) assert {"123", "456", "789"} == my_task.file_dep def test_file_dep_str(self): pytest.raises(task.InvalidTask, task.Task, "Task X", ["taskcmd"], file_dep=[['aaaa']]) def test_file_dep_unicode(self): unicode_name = "中文" my_task = task.Task("Task X", ["taskcmd"], file_dep=[unicode_name]) assert unicode_name in my_task.file_dep class TestTaskDeps(object): def test_task_dep(self): my_task = task.Task("Task X", ["taskcmd"], task_dep=["123","4*56"]) assert ["123"] == my_task.task_dep assert ["4*56"] == my_task.wild_dep def test_calc_dep(self): my_task = task.Task("Task X", ["taskcmd"], calc_dep=["123"]) assert set(["123"]) == my_task.calc_dep def test_update_deps(self): my_task = task.Task("Task X", ["taskcmd"], file_dep=["fileX"], calc_dep=["calcX"], uptodate=[None]) my_task.update_deps({'file_dep': ['fileY'], 'task_dep': ['taskY'], 'calc_dep': ['calcX', 'calcY'], 'uptodate': [True], 'to_be_ignored': 'asdf', }) assert set(['fileX', 'fileY']) == my_task.file_dep assert ['taskY'] == my_task.task_dep assert set(['calcX', 'calcY']) == my_task.calc_dep assert [(None, None, None), (True, None, None)] == my_task.uptodate class TestTaskTargets(object): def test_targets_can_be_path(self): my_task = task.Task("Task X", ["taskcmd"], targets=["123", Path("456"), PurePath("789")]) assert ["123", "456", "789"] == my_task.targets def test_targets_should_be_string_or_path(self): assert pytest.raises(task.InvalidTask, task.Task, "Task X", ["taskcmd"], targets=["123", Path("456"), 789]) class TestTask_Loader(object): def test_delayed_after_execution(self): # after `executed` creates an implicit task_dep delayed = task.DelayedLoader(lambda: None, executed='foo') t1 = task.Task('bar', None, loader=delayed) assert t1.task_dep == ['foo'] class TestTask_Getargs(object): def test_ok(self): getargs = {'x' : ('t1','x'), 'y': ('t2','z')} t = task.Task('t3', None, getargs=getargs) assert len(t.uptodate) == 2 assert ['t1', 't2'] == sorted([t.uptodate[0][0].dep_name, t.uptodate[1][0].dep_name]) def test_invalid_desc(self): getargs = {'x' : 't1'} assert pytest.raises(task.InvalidTask, task.Task, 't3', None, getargs=getargs) def test_invalid_desc_tuple(self): getargs = {'x' : ('t1',)} assert pytest.raises(task.InvalidTask, task.Task, 't3', None, getargs=getargs) class TestTaskTitle(object): def test_title(self): t = task.Task("MyName",["MyAction"]) assert "MyName" == t.title() def test_custom_title(self): t = task.Task("MyName",["MyAction"], title=(lambda x: "X%sX" % x.name)) assert "X%sX"%str(t.name) == t.title(), t.title() class TestTaskRepr(object): def test_repr(self): t = task.Task("taskX",None,('t1','t2')) assert "" == repr(t), repr(t) class TestTaskActions(object): def test_success(self): t = task.Task("taskX", [PROGRAM]) t.execute(Stream(0)) def test_result(self): # task.result is the value of last action t = task.Task('t1', ["%s hi_list hi1" % PROGRAM, "%s hi_list hi2" % PROGRAM]) t.dep_changed = [] t.execute(Stream(0)) assert "hi_listhi2" == t.result def test_values(self): def return_dict(d): return d # task.result is the value of last action t = task.Task('t1', [(return_dict, [{'x':5}]), (return_dict, [{'y':10}]),]) t.execute(Stream(0)) assert {'x':5, 'y':10} == t.values def test_failure(self): t = task.Task("taskX", ["%s 1 2 3" % PROGRAM]) got = t.execute(Stream(0)) assert isinstance(got, TaskError) # make sure all cmds are being executed. def test_many(self): t = task.Task("taskX",["%s hi_stdout hi2" % PROGRAM, "%s hi_list hi6" % PROGRAM]) t.dep_changed = [] t.execute(Stream(0)) got = "".join([a.out for a in t.actions]) assert "hi_stdouthi_list" == got, repr(got) def test_fail_first(self): t = task.Task("taskX", ["%s 1 2 3" % PROGRAM, PROGRAM]) got = t.execute(Stream(0)) assert isinstance(got, TaskError) def test_fail_second(self): t = task.Task("taskX", ["%s 1 2" % PROGRAM, "%s 1 2 3" % PROGRAM]) got = t.execute(Stream(0)) assert isinstance(got, TaskError) # python and commands mixed on same task def test_mixed(self): def my_print(msg): print(msg, end='') t = task.Task("taskX",["%s hi_stdout hi2" % PROGRAM, (my_print,['_PY_']), "%s hi_list hi6" % PROGRAM]) t.dep_changed = [] t.execute(Stream(0)) got = "".join([a.out for a in t.actions]) assert "hi_stdout_PY_hi_list" == got, repr(got) class TestTaskTeardown(object): def test_ok(self): got = [] def put(x): got.append(x) t = task.Task('t1', [], teardown=[(put, [1]), (put, [2])]) t.execute(Stream(0)) assert None == t.execute_teardown(Stream(0)) assert [1,2] == got def test_fail(self): def my_raise(): raise Exception('hoho') t = task.Task('t1', [], teardown=[(my_raise,)]) t.execute(Stream(0)) got = t.execute_teardown(Stream(0)) assert isinstance(got, BaseFail) class TestTaskClean(object): @pytest.fixture def tmpdir(self, request): tmpdir = {} tmpdir['dir'] = tempfile.mkdtemp(prefix='doit-') tmpdir['subdir'] = tempfile.mkdtemp(dir=tmpdir['dir']) files = [os.path.join(tmpdir['dir'], fname) for fname in ['a.txt', 'b.txt', os.path.join(tmpdir['subdir'], 'c.txt')]] tmpdir['files'] = files # create empty files for filename in tmpdir['files']: open(filename, 'a').close() def remove_tmpdir(): if os.path.exists(tmpdir['dir']): shutil.rmtree(tmpdir['dir']) request.addfinalizer(remove_tmpdir) return tmpdir def test_clean_nothing(self, tmpdir): t = task.Task("xxx", None) assert False == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: assert os.path.exists(filename) def test_clean_targets(self, tmpdir): t = task.Task("xxx", None, targets=tmpdir['files'], clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: assert not os.path.exists(filename), filename def test_clean_non_existent_targets(self): t = task.Task('xxx', None, targets=["i_dont_exist"], clean=True) t.clean(StringIO(), False) # nothing is raised def test_clean_empty_dirs(self, tmpdir): # Remove empty directories listed in targets targets = tmpdir['files'] + [tmpdir['subdir']] t = task.Task("xxx", None, targets=targets, clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: assert not os.path.exists(filename) assert not os.path.exists(tmpdir['subdir']) assert os.path.exists(tmpdir['dir']) def test_keep_non_empty_dirs(self, tmpdir): # Keep non empty directories listed in targets targets = [tmpdir['files'][0], tmpdir['dir']] t = task.Task("xxx", None, targets=targets, clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: expected = not filename in targets assert expected == os.path.exists(filename) assert os.path.exists(tmpdir['dir']) def test_clean_any_order(self, tmpdir): # Remove targets in reverse lexical order so that subdirectories' order # in the targets array is irrelevant targets = tmpdir['files'] + [tmpdir['dir'], tmpdir['subdir']] t = task.Task("xxx", None, targets=targets, clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: assert not os.path.exists(filename) assert not os.path.exists(tmpdir['dir']) assert not os.path.exists(tmpdir['subdir']) def test_clean_actions(self, tmpdir): # a clean action can be anything, it can even not clean anything! c_path = tmpdir['files'][0] def say_hello(): fh = open(c_path, 'a') fh.write("hello!!!") fh.close() t = task.Task("xxx",None,targets=tmpdir['files'], clean=[(say_hello,)]) assert False == t._remove_targets assert 1 == len(t.clean_actions) t.clean(StringIO(), False) for filename in tmpdir['files']: assert os.path.exists(filename) fh = open(c_path, 'r') got = fh.read() fh.close() assert "hello!!!" == got def test_clean_action_error(self, capsys): def fail_clean(): 5/0 t = task.Task("xxx", None, clean=[(fail_clean,)]) assert 1 == len(t.clean_actions) t.clean(StringIO(), dryrun=False) err = capsys.readouterr()[1] assert "PythonAction Error" in err def test_clean_action_kwargs(self): def fail_clean(dryrun): print('hello %s' % dryrun) t = task.Task("xxx", None, clean=[(fail_clean,)]) assert 1 == len(t.clean_actions) out = StringIO() t.clean(out, dryrun=False) assert "hello False" in out.getvalue() def test_dryrun_file(self, tmpdir): t = task.Task("xxx", None, targets=tmpdir['files'], clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), True) # files are NOT removed for filename in tmpdir['files']: assert os.path.exists(filename), filename def test_dryrun_dir(self, tmpdir): targets = tmpdir['files'] + [tmpdir['dir']] for filename in tmpdir['files']: os.remove(filename) t = task.Task("xxx", None, targets=targets, clean=True) assert True == t._remove_targets assert 0 == len(t.clean_actions) t.clean(StringIO(), True) assert os.path.exists(tmpdir['dir']) def test_dryrun_actions_not_executed(self, tmpdir): # clean action is not executed at all if it does not contain # a `dryrun` parameter self.executed = False def say_hello(): self.executed = True t = task.Task("xxx", None, targets=tmpdir['files'], clean=[(say_hello,)]) assert False == t._remove_targets assert 1 == len(t.clean_actions) t.clean(StringIO(), True) assert not self.executed def test_dryrun_actions_with_param_true(self, tmpdir): # clean action is not executed at all if it does not contain # a `dryrun` parameter self.executed = False self.dryrun_val = None def say_hello(dryrun): self.executed = True self.dryrun_val = dryrun t = task.Task("xxx", None, targets=tmpdir['files'], clean=[(say_hello,)]) assert False == t._remove_targets assert 1 == len(t.clean_actions) t.clean(StringIO(), dryrun=True) assert self.executed is True assert self.dryrun_val is True def test_dryrun_actions_with_param_false(self, tmpdir): # clean action is not executed at all if it does not contain # a `dryrun` parameter self.executed = False self.dryrun_val = None def say_hello(dryrun): self.executed = True self.dryrun_val = dryrun t = task.Task("xxx", None, targets=tmpdir['files'], clean=[(say_hello,)]) assert False == t._remove_targets assert 1 == len(t.clean_actions) t.clean(StringIO(), dryrun=False) assert self.executed is True assert self.dryrun_val is False class TestTaskDoc(object): def test_no_doc(self): t = task.Task("name", ["action"]) assert '' == t.doc def test_single_line(self): t = task.Task("name", ["action"], doc=" i am doc") assert "i am doc" == t.doc def test_multiple_lines(self): t = task.Task("name", ["action"], doc="i am doc \n with many lines\n") assert "i am doc" == t.doc def test_start_with_empty_lines(self): t = task.Task("name", ["action"], doc="\n\n i am doc \n") assert "i am doc" == t.doc def test_just_new_line(self): t = task.Task("name", ["action"], doc=" \n \n\n") assert "" == t.doc class TestTaskPickle(object): def test_geststate(self): t = task.Task("my_name", ["action"]) pd = t.__getstate__() assert None == pd['uptodate'] assert None == pd['_action_instances'] def test_safedict(self): t = task.Task("my_name", ["action"]) pd = t.pickle_safe_dict() assert 'uptodate' not in pd assert '_action_instances' not in pd assert 'value_savers' not in pd assert 'clean_actions' not in pd class TestTaskUpdateFromPickle(object): def test_change_value(self): t = task.Task("my_name", ["action"]) assert {} == t.values class FakePickle(): def __init__(self): self.values = [1,2,3] t.update_from_pickle(FakePickle().__dict__) assert [1,2,3] == t.values assert 'my_name' == t.name class TestDictToTask(object): def testDictOkMinimum(self): dict_ = {'name':'simple','actions':['xpto 14']} assert isinstance(task.dict_to_task(dict_), task.Task) def testDictFieldTypo(self): dict_ = {'name':'z','actions':['xpto 14'],'typo_here':['xxx']} pytest.raises(action.InvalidTask, task.dict_to_task, dict_) def testDictMissingFieldAction(self): pytest.raises(action.InvalidTask, task.dict_to_task, {'name':'xpto 14'}) class TestResultDep(object): def test_single(self, dep_manager): tasks = {'t1': task.Task("t1", None, uptodate=[task.result_dep('t2')]), 't2': task.Task("t2", None), } # _config_task was executed and t2 added as task_dep assert ['t2'] == tasks['t1'].task_dep # first t2 result tasks['t2'].result = 'yes' dep_manager.save_success(tasks['t2']) assert 'run' == dep_manager.get_status(tasks['t1'], tasks).status # first time tasks['t1'].save_extra_values() dep_manager.save_success(tasks['t1']) assert 'up-to-date' == dep_manager.get_status(tasks['t1'], tasks).status # t2 result changed tasks['t2'].result = '222' dep_manager.save_success(tasks['t2']) assert 'run' == dep_manager.get_status(tasks['t1'], tasks).status tasks['t1'].save_extra_values() dep_manager.save_success(tasks['t1']) assert 'up-to-date' == dep_manager.get_status(tasks['t1'], tasks).status def test_group(self, dep_manager): tasks = {'t1': task.Task("t1", None, uptodate=[task.result_dep('t2')]), 't2': task.Task("t2", None, task_dep=['t2:a', 't2:b'], has_subtask=True), 't2:a': task.Task("t2:a", None), 't2:b': task.Task("t2:b", None), } # _config_task was executed and t2 added as task_dep assert ['t2'] == tasks['t1'].task_dep # first t2 result tasks['t2:a'].result = 'yes1' dep_manager.save_success(tasks['t2:a']) tasks['t2:b'].result = 'yes2' dep_manager.save_success(tasks['t2:b']) assert 'run' == dep_manager.get_status(tasks['t1'], tasks).status # first time tasks['t1'].save_extra_values() dep_manager.save_success(tasks['t1']) assert 'up-to-date' == dep_manager.get_status(tasks['t1'], tasks).status # t2 result changed tasks['t2:a'].result = '222' dep_manager.save_success(tasks['t2:a']) assert 'run' == dep_manager.get_status(tasks['t1'], tasks).status tasks['t1'].save_extra_values() dep_manager.save_success(tasks['t1']) assert 'up-to-date' == dep_manager.get_status(tasks['t1'], tasks).status doit-0.36.0/tests/test_tools.py000066400000000000000000000252231423054503100164530ustar00rootroot00000000000000import os import datetime import json import operator from sys import executable import pytest from doit import exceptions from doit import tools from doit import task class TestCreateFolder(object): def test_create_folder(self): def rm_dir(): if os.path.exists(DIR_DEP): os.removedirs(DIR_DEP) DIR_DEP = os.path.join(os.path.dirname(__file__),"parent/child/") rm_dir() tools.create_folder(DIR_DEP) assert os.path.exists(DIR_DEP) rm_dir() def test_error_if_path_is_a_file(self): def rm_file(path): if os.path.exists(path): os.remove(path) path = os.path.join(os.path.dirname(__file__), "test_create_folder") with open(path, 'w') as fp: fp.write('testing') pytest.raises(OSError, tools.create_folder, path) rm_file(path) class TestTitleWithActions(object): def test_actions(self): t = task.Task("MyName",["MyAction"], title=tools.title_with_actions) assert "MyName => Cmd: MyAction" == t.title() def test_group(self): t = task.Task("MyName", None, file_dep=['file_foo'], task_dep=['t1','t2'], title=tools.title_with_actions) assert "MyName => Group: t1, t2" == t.title() class TestRunOnce(object): def test_run(self): t = task.Task("TaskX", None, uptodate=[tools.run_once]) assert False == tools.run_once(t, t.values) t.save_extra_values() assert True == tools.run_once(t, t.values) class TestConfigChanged(object): def test_invalid_type(self): class NotValid(object):pass uptodate = tools.config_changed(NotValid()) pytest.raises(Exception, uptodate, None, None) def test_string(self): ua = tools.config_changed('a') ub = tools.config_changed('b') t1 = task.Task("TaskX", None, uptodate=[ua]) assert False == ua(t1, t1.values) assert False == ub(t1, t1.values) t1.save_extra_values() assert True == ua(t1, t1.values) assert False == ub(t1, t1.values) def test_unicode(self): ua = tools.config_changed({'x': "中文"}) ub = tools.config_changed('b') t1 = task.Task("TaskX", None, uptodate=[ua]) assert False == ua(t1, t1.values) assert False == ub(t1, t1.values) t1.save_extra_values() assert True == ua(t1, t1.values) assert False == ub(t1, t1.values) def test_dict(self): ua = tools.config_changed({'x':'a', 'y':1}) ub = tools.config_changed({'x':'b', 'y':1}) t1 = task.Task("TaskX", None, uptodate=[ua]) assert False == ua(t1, t1.values) assert False == ub(t1, t1.values) t1.save_extra_values() assert True == ua(t1, t1.values) assert False == ub(t1, t1.values) def test_nested_dict(self): # actually both dictionaries contain same values # but nested dictionary keys are in a different order c1a = tools.config_changed({'x':'a', 'y':{'one':1, 'two':2}}) c1b = tools.config_changed({'y':{'two':2, 'one':1}, 'x':'a'}) t1 = task.Task("TaskX", None, uptodate=[c1a]) assert False == c1a(t1, t1.values) t1.save_extra_values() assert True == c1a(t1, t1.values) assert True == c1b(t1, t1.values) def test_using_custom_encoder(self): class DatetimeJSONEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, datetime.datetime): return o.isoformat() ua = tools.config_changed({'a': datetime.datetime(2018, 12, 10, 10, 33, 55, 478421), 'b': 'bb'}, encoder=DatetimeJSONEncoder) ub = tools.config_changed({'a': datetime.datetime.now(), 'b': 'bb'}, encoder=DatetimeJSONEncoder) t1 = task.Task("TaskX", None, uptodate=[ua]) assert ua(t1, t1.values) is False assert ub(t1, t1.values) is False t1.save_extra_values() assert ua(t1, t1.values) is True assert ub(t1, t1.values) is False class TestTimeout(object): def test_invalid(self): pytest.raises(Exception, tools.timeout, "abc") def test_int(self, monkeypatch): monkeypatch.setattr(tools.time_module, 'time', lambda: 100) uptodate = tools.timeout(5) t = task.Task("TaskX", None, uptodate=[uptodate]) assert False == uptodate(t, t.values) t.save_extra_values() assert 100 == t.values['success-time'] monkeypatch.setattr(tools.time_module, 'time', lambda: 103) assert True == uptodate(t, t.values) monkeypatch.setattr(tools.time_module, 'time', lambda: 106) assert False == uptodate(t, t.values) def test_timedelta(self, monkeypatch): monkeypatch.setattr(tools.time_module, 'time', lambda: 10) limit = datetime.timedelta(minutes=2) uptodate = tools.timeout(limit) t = task.Task("TaskX", None, uptodate=[uptodate]) assert False == uptodate(t, t.values) t.save_extra_values() assert 10 == t.values['success-time'] monkeypatch.setattr(tools.time_module, 'time', lambda: 100) assert True == uptodate(t, t.values) monkeypatch.setattr(tools.time_module, 'time', lambda: 200) assert False == uptodate(t, t.values) def test_timedelta_big(self, monkeypatch): monkeypatch.setattr(tools.time_module, 'time', lambda: 10) limit = datetime.timedelta(days=2, minutes=5) uptodate = tools.timeout(limit) t = task.Task("TaskX", None, uptodate=[uptodate]) assert False == uptodate(t, t.values) t.save_extra_values() assert 10 == t.values['success-time'] monkeypatch.setattr(tools.time_module, 'time', lambda: 3600 * 30) assert True == uptodate(t, t.values) monkeypatch.setattr(tools.time_module, 'time', lambda: 3600 * 49) assert False == uptodate(t, t.values) @pytest.fixture def checked_file(request): fname = 'mytmpfile' file_ = open(fname, 'a') file_.close() def remove(): os.remove(fname) request.addfinalizer(remove) return fname class TestCheckTimestampUnchanged(object): def test_time_selection(self): check = tools.check_timestamp_unchanged('check_atime', 'atime') assert 'st_atime' == check._timeattr check = tools.check_timestamp_unchanged('check_ctime', 'ctime') assert 'st_ctime' == check._timeattr check = tools.check_timestamp_unchanged('check_mtime', 'mtime') assert 'st_mtime' == check._timeattr pytest.raises( ValueError, tools.check_timestamp_unchanged, 'check_invalid_time', 'foo') def test_file_missing(self): check = tools.check_timestamp_unchanged('no_such_file') t = task.Task("TaskX", None, uptodate=[check]) # fake values saved from previous run task_values = {check._key: 1} # needs any value different from None pytest.raises(OSError, check, t, task_values) def test_op_ge(self, monkeypatch, checked_file): check = tools.check_timestamp_unchanged(checked_file,cmp_op=operator.ge) t = task.Task("TaskX", None, uptodate=[check]) # no stored value/first run assert False == check(t, t.values) # value just stored is equal to itself t.save_extra_values() assert True == check(t, t.values) # stored timestamp less than current, up to date future_time = list(t.values.values())[0] + 100 monkeypatch.setattr(check, '_get_time', lambda: future_time) assert False == check(t, t.values) def test_op_bad_custom(self, monkeypatch, checked_file): # handling misbehaving custom operators def bad_op(prev_time, current_time): raise Exception('oops') check = tools.check_timestamp_unchanged(checked_file, cmp_op=bad_op) t = task.Task("TaskX", None, uptodate=[check]) # fake values saved from previous run task_values = {check._key: 1} # needs any value different from None pytest.raises(Exception, check, t, task_values) def test_multiple_checks(self): # handling multiple checks on one file (should save values in such way # they don't override each other) check_a = tools.check_timestamp_unchanged('check_multi', 'atime') check_m = tools.check_timestamp_unchanged('check_multi', 'mtime') assert check_a._key != check_m._key class TestLongRunning(object): def test_success(self): TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) my_action = tools.LongRunning(PROGRAM + " please fail") got = my_action.execute() assert got is None def test_ignore_keyboard_interrupt(self, monkeypatch): my_action = tools.LongRunning('') class FakeRaiseInterruptProcess(object): def __init__(self, *args, **kwargs): pass def wait(self): raise KeyboardInterrupt() monkeypatch.setattr(tools.subprocess, 'Popen', FakeRaiseInterruptProcess) got = my_action.execute() assert got is None class TestInteractive(object): def test_fail(self): TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) my_action = tools.Interactive(PROGRAM + " please fail") got = my_action.execute() assert isinstance(got, exceptions.TaskFailed) def test_success(self): TEST_PATH = os.path.dirname(__file__) PROGRAM = "%s %s/sample_process.py" % (executable, TEST_PATH) my_action = tools.Interactive(PROGRAM + " ok") got = my_action.execute() assert got is None class TestPythonInteractiveAction(object): def test_success(self): def hello(): print('hello') my_action = tools.PythonInteractiveAction(hello) got = my_action.execute() assert got is None def test_ignore_keyboard_interrupt(self, monkeypatch): def raise_x(): raise Exception('x') my_action = tools.PythonInteractiveAction(raise_x) got = my_action.execute() assert isinstance(got, exceptions.TaskError) def test_returned_dict_saved_result_values(self): def val(): return {'x': 3} my_action = tools.PythonInteractiveAction(val) got = my_action.execute() assert got is None assert my_action.result == {'x': 3} assert my_action.values == {'x': 3} def test_returned_string_saved_result(self): def val(): return 'hello' my_action = tools.PythonInteractiveAction(val) got = my_action.execute() assert got is None assert my_action.result == 'hello' doit-0.36.0/zsh_completion_doit000066400000000000000000000312751423054503100165430ustar00rootroot00000000000000#compdef doit _doit() { local -a commands tasks # format is 'completion:description' commands=( 'auto: automatically execute tasks when a dependency changes' 'clean: clean action / remove targets' 'dumpdb: dump dependency DB' 'forget: clear successful run status from internal DB' 'help: show help' 'ignore: ignore task (skip) on subsequent runs' 'info: show info about a task' 'list: list tasks from dodo file' 'reset-dep: recompute and save the state of file dependencies without executing actions' 'run: run tasks' 'strace: use strace to list file_deps and targets' 'tabcompletion: generate script for tab-completion' ) # split output by lines to create an array tasks=("${(f)$(doit list --template '{name}: {doc}')}") # complete command or task name if (( CURRENT == 2 )); then _arguments -A : '::cmd:(($commands))' '::task:(($tasks))' return fi # revome program name from $words and decrement CURRENT local curcontext context state state_desc line _arguments -C '*:: :->' # complete sub-command or task options local -a _command_args case "$words[1]" in (auto) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-v|--verbosity)'{-v,--verbosity}'[0 capture (do not print) stdout/stderr from task. 1 capture stdout only. 2 do not capture anything (print everything immediately). [default: 1\]]' \ '*::task:(($tasks))' '' ) ;; (clean) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-c|--clean-dep)'{-c,--clean-dep}'[clean task dependencies too]' \ '(-a|--clean-all)'{-a,--clean-all}'[clean all task]' \ '(-n|--dry-run)'{-n,--dry-run}'[print actions without really executing them]' \ '*::task:(($tasks))' '' ) ;; (dumpdb) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '' ) ;; (forget) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-s|--follow-sub)'{-s,--follow-sub}'[forget task dependencies too]' \ '*::task:(($tasks))' '' ) ;; (help) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '*::task:(($tasks))' '::cmd:(($commands))' '' ) ;; (ignore) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '*::task:(($tasks))' '' ) ;; (info) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '*::task:(($tasks))' '' ) ;; (list) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '--all[list include all sub-tasks from dodo file]' \ '(-q|--quiet)'{-q,--quiet}'[print just task name (less verbose than default)]' \ '(-s|--status)'{-s,--status}'[print task status (R)un, (U)p-to-date, (I)gnored]' \ '(-p|--private)'{-p,--private}'[print private tasks (start with '_')]' \ '--deps[print list of dependencies (file dependencies only)]' \ '--template[display entries with template]' \ '*::task:(($tasks))' '' ) ;; (reset-dep) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '*::task:(($tasks))' '' ) ;; (run) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-a|--always-execute)'{-a,--always-execute}'[always execute tasks even if up-to-date [default: %(default)s\]]' \ '(-c|--continue)'{-c,--continue}'[continue executing tasks even after a failure [default: %(default)s\]]' \ '(-v|--verbosity)'{-v,--verbosity}'[0 capture (do not print) stdout/stderr from task. 1 capture stdout only. 2 do not capture anything (print everything immediately). [default: 1\]]' \ '(-r|--reporter)'{-r,--reporter}'[Choose output reporter. [default: %(default)s\]]' \ '(-o|--output-file)'{-o,--output-file}'[write output into file [default: stdout\]]' \ '(-n|--process)'{-n,--process}'[number of subprocesses [default: %(default)s\]]' \ '(-P|--parallel-type)'{-P,--parallel-type}'[Tasks can be executed in parallel in different ways: 'process': uses python multiprocessing module 'thread': uses threads [default: %(default)s\] ]' \ '--pdb[get into PDB (python debugger) post-mortem in case of unhandled exception]' \ '(-s|--single)'{-s,--single}'[Execute only specified tasks ignoring their task_dep [default: %(default)s\]]' \ '*::task:(($tasks))' '' ) ;; (strace) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-a|--all)'{-a,--all}'[display all files (not only from within CWD path)]' \ '--keep[save strace command output into strace.txt]' \ '*::task:(($tasks))' '' ) ;; (tabcompletion) _command_args=( '--db-file[file used to save successful runs [default: %(default)s\]]' \ '--backend[Select dependency file backend. [default: %(default)s\]]' \ '--check_file_uptodate[Choose how to check if files have been modified. Available options [default: %(default)s\]: 'md5': use the md5sum 'timestamp': use the timestamp ]' \ '(-f|--file)'{-f,--file}'[load task from dodo FILE [default: %(default)s\]]' \ '(-d|--dir)'{-d,--dir}'[set path to be used as cwd directory (file paths on dodo file are relative to dodo.py location).]' \ '(-k|--seek-file)'{-k,--seek-file}'[seek dodo file on parent folders [default: %(default)s\]]' \ '(-s|--shell)'{-s,--shell}'[Completion code for SHELL. [default: %(default)s\]]' \ '--hardcode-tasks[Hardcode tasks from current task list.]' \ '' ) ;; # default completes task names (*) _command_args='*::task:(($tasks))' ;; esac # -A no options will be completed after the first non-option argument _arguments -A : $_command_args return 0 } _doit